English | 中文版
[TOC]
The inheritance type controls how base class members are inherited:
-
Public Inheritance
Base Member Access in Derived External Access publicpublic✅ Yes protectedprotected❌ No privateInaccessible ❌ No class Base { public: int pub; protected: int prot; private: int priv; }; class Derived : public Base { void func() { pub = 1; // ✅ public prot = 2; // ✅ protected priv = 3; // ❌ inaccessible } };
-
Protected Inheritance
Base Member Access in Derived External Access publicprotected❌ No protectedprotected❌ No privateInaccessible ❌ No class Base { public: int pub; protected: int prot; private: int priv; }; class Derived : protected Base { void func() { pub = 1; // ✅ becomes protected prot = 2; // ✅ protected priv = 3; // ❌ inaccessible } }; Derived obj; obj.pub = 5; // ❌ Error
-
Private Inheritance
Base Member Access in Derived External Access publicprivate❌ No protectedprivate❌ No privateInaccessible ❌ No class Base { public: int pub; protected: int prot; private: int priv; }; class Derived : private Base { void func() { pub = 1; // ✅ becomes private prot = 2; // ✅ becomes private priv = 3; // ❌ inaccessible } }; Derived obj; obj.pub = 5; // ❌ Error
Specifier Access Summary:
| Specifier | Inside Class | Derived Class | Outside Class | Default for class |
Default for struct |
|---|---|---|---|---|---|
public |
✅ Yes | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
protected |
✅ Yes | ✅ Yes | ❌ No | ❌ No | ❌ No |
private |
✅ Yes | ❌ No | ❌ No | ✅ Yes | ❌ No |
struct vs class:
struct defaults to public, class defaults to private; otherwise they are identical.
[Top](#C++ Object Oriented Programming)
graph LR
subgraph Class
C1(static xx)
C2(stataic xx = xxx)
C3(T xx)
subgraph Virtual Table
V1(1000: type_info for ObjectClass)
V2(1001: virtual function1)
V3(1002: virtual function2)
end
end
subgraph Object
subgraph Object Inside
O1(xx)
C3 --inheritance--> O1
end
subgraph Object Outside
ptr
vptr -..->V1
end
end
subgraph Function Member Table
F1(function1)
F2(function2)
F3(function3)
V2 -..-> F1
V3 -..-> F2
ptr -..-> F3
end
subgraph DS
C2 -..-> xxx
end
subgraph BSS
C1 -..-> xx
end
type_info for ObjectClassis the type of the class, which must be located at the first index of the virtual table to support RTTI (Runtime Type Identification);class typeis a string or number representing the class type;BSS(Block Start By Symbol);DS(Data Segment);Virtual Tablecontains virtual function pointers;
-
Static member variables are stored in
data segment, independent ofclass object; -
Non-static members are stored directly in each class object, so they can only be accessed through a class object (or
*this); -
To access a non-static member variable, the compiler adds the class object's address plus the data member's offset (offset), for example:
&origin._y = 0.0; &origin + (&Point::_y - 1) // &Point::_y = offset + 1
-
C++ standard requires: members declared later have higher positions in the class object, not necessarily contiguous;
-
Taking the address of a
static memberyields a pointer to that data type, not a pointer to a class member; -
Using virtual functions introduces the following overhead:
- Introduces virtual table to store each declared virtual function, plus one slot at the beginning (to support RTTI);
- Each class object introduces a vptr to provide runtime linking, allowing each class object to find the virtual function table;
- Enhances constructor to initialize vptr pointing to the virtual table;
- Enhances destructor to clear the pointer to the virtual table.
-
To maintain C language compatibility, vptr is usually placed at the end of the class object;
-
Each class has exactly one virtual table (vtable) containing addresses of virtual functions, and each object has a vptr (virtual table pointer) pointing to the vtable; for example:
ptr->normalize()is internally converted to(*ptr->vptr[1])(ptr); -
The order of functions in the virtual function table follows the declaration order;
The compiler internally converts member functions to non-member functions in the following process:
-
Add
thispointer as the first parameter of the functionIf the member function is const, the type of
thispointer is:const className* const; and access to eachnonstatic data memberis performed through thethispointer; -
Function name mangling
(name mangling), recoding the name according to the pattern
function name + parameter list + parameter types, making it a unique name; -
NRV optimization
(Name Return Value), if there's a return value, add a reference type parameter to replace the return value;
Example:
Point setPoint(const Point& point);Converted to:
void setPoint_crefpoint(const Point* this, const Point& point, Point& __result);Static members do not have a this pointer, with the following characteristics:
- Cannot directly access
nonstatic membersin the class; - Cannot be declared as
const,volatile, orvirtual; - Do not need to be called through a
class object, can be called directly viaclassname::syntax.
If the address of a static member function is taken, the address type obtained is "nonmember function pointer" instead of "class member function pointer";
Example, static function conversion:
static int Point3d::object_count() { return _object_count; }Converted to:
int object_count__5Point3dSFV() { return _object_count_5Point3dSFV; }Example, getting address of static member function:
&Point3d::object_count();The type obtained is:
int(*)();
// not int(Point3d::*)();-
Taking the address of a virtual member function only yields an index value;
class Point { public: virtual ~Point(); float x(); float y(); virtual float z(); } &Point::~Point(); // get index 1 &Point::z(); // get index 2 &Point::x(); // get function address in memory // call z() through function pointer float (Point::*pmf)() = &Point::z(); Point* ptr; (ptr->*pmg)(); // internally converted to (*ptr->vptr[(int)pmf])(ptr);
Inline functions are only a request to the compiler; the compiler itself decides whether to actually inline. Usually the compiler calculates the total number of operations like assignments, function calls, virtual function calls to decide whether to inline.
Two general phases for handling inline function:
- Analyze the function definition to determine the function's
"instrinsic inline ability"; - Actual inline function expansion occurs at the call site.
Inline function expansion of formal arguments;
example:
inline int min(int i, int j) { return i < j ? i : j; }
inline int bar() {
int minval;
int val1 = 1024;
int val2 = 2048;
minval = min(val1, val2); // converted to minval = val1 < val2 ? val1 : val2;
minval = min(1024, 2048); // converted to minval = 1024;
minval = min(foo(), bar()+1); // converted to int t1,t2; minval = (t1=foo()), (t2=bar()+1), t1<t2?t1:t2;
return minval;
}Inline function expansion of local variables;
example:
inline int min(int i, int j) {
int minval = i < j ? i : j;
return minval;
}
inline int bar() {
int minval;
int val1 = 1024;
int val2 = 2048;
minval = min(val1, val2); // converted to
// int __min_lv_minval;
// int minval = (__min_lv_minval = val1 < val2 ? val1 : val2), __min_lv_minval);
return minval;
}If an inline function parameter has side effects or multiple calls, or the function has multiple local variables, this will produce temporary objects and result in extensively expanded code, causing program size to explode;
example:
int minval = min(val1, val2) + min(foo(), foo() + 1);Expands to:
// temporary objects produced for local variables
int __min_lv_minval_00, __min_lv_minval_01;
// temporary variables produced for side effects
int t1, t2;
int minval = (__min_lv_minval_00 = val1 < val2 ? val1 : val2),
__min_lv_minval_00)
+
((__min_lv_minval_01=(t1=foo()), (t2=bar()+1),
t1<t2?t1:t2),__min_lv_minval_00);The memory size of a class object is calculated by the formula:
-
$N$ : size ofnonstatic data members; -
$P$ : space filled due to memory alignment (adjusting values to multiples of certain numbers; on 32-bit computers, alignment is usually 4 bytes (32 bits) to maximize bus "transportation" efficiency); -
$V$ : any additional overhead (overhead) produced internally to supportvirtual.
An empty class usually has a size of 1, because the compiler inserts a char to allow any two objects of the class to have unique addresses in memory.
Empty base class optimization (EBCO), Compiler doesn't allocate space for empty base classes if they don't increase alignment.
When EBCO works:
| Scenario | Works? | Reason |
|---|---|---|
| Single empty base class | ✅ Yes | Can occupy zero space |
| Multiple distinct empty bases | ✅ Yes | Each is a different type |
| Empty Base Classes with Different Access Specifiers | ✅ Yes | Do not force different layout for EBCO. |
| Multiple identical empty bases | ❌ No | Need distinct addresses |
| Empty as a member(not base) | ❌ No | Members can't be optimized until C++20 |
| Empty with virtual functions | ❌ No | Has vtable pointer (not empty) |
| Empty with alignment requirements | Alignment forces padding | |
Empty base with [[no_unique_address]](C++20) |
✅ Yes (for members) | Standardized member optimization |
| Empty base in standard layout | ✅ Yes | But imposes restrictions |
| Empty base in template with same type twice | ❌ No | Same type needs distinct addresses |
| Empty base with non-empty base | Address uniqueness requirements |
-
Multiple identical empty bases
TODO -
Empty as a member(not base)
// test environment: MACOS aarch 64bit struct Base {}; struct NoEBCO1 { Base base; // not a inheritance base - take 1 byte int x; // 4 bytes // Total size: 1 (base) + 4 (x) + padding = 8 bytes }; struct NoEBCO2 { int x; // 4 bytes Base base; // not a inheritance base - take 1 byte // Total size: 1 (base) + 4 (x) + padding = 8 bytes }; struct WithEBCO : Base { int x; // 4 bytes // Total size: 0 (EBCO) + 4 (x) = 4 bytes };
-
Empty with virtual functions
TODO -
Empty base in template with same type twice
TODO
TODO
-
C++ standard forbids objects of size 0, each object must have a unique address, so the compiler adds 1 byte (dummy byte) as a placeholder
class A {}; sizeof(A); // 1 byte A a1, a2; &a1 != &a2; // ✅ Must be different addresses
-
EBCP fails most commonly when:
Multiple inheritance of the same empty base type (need distinct addresses).- Empty classes as members instead of bases (until C++20
[[no_unique_address]]) Empty classes with virtual functions (not actually empty - have vptr)Empty classes with alignment requirements (alignment forces padding)
Space Calculation Example:
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
};
class Bear : public ZooAnimal {
public:
Bear();
~Bear();
// ...
void rotate();
virtual void dance();
// ...
protected:
enum Dances { ... };
Dances dances_known;
int cell_block;
};
Bear b( "Yogi" );
Bear *pb = &b;
Bear &rb = *pb;[Top](#C++ Object Oriented Programming)
- The process of the new operator is: first allocate memory, then call the constructor (built-in types are directly assigned). (If memory allocation fails, the memory still needs to be released.)
- The process of the delete operator is: first call the destructor (built-in types do not have this step), then release memory.
Example, new operation:
Point3d *origin = new Point3d;Can be split into the following steps:
Point3d *origin;
if(origin = __new(sizeof(Point3d)))
try {
origin->Point3d::Point3d(origin);
}
catch(...) {
__delete(origin)
throw;
}
}Example, delete operation:
delete origin;Can be split into the following steps:
if(origin != 0) {
Point3d::~Point3d(origin);
__delete(origin);
}Placement Operator new is a predefined overloaded new operator with the following prototype:
void* operator new(size_t, void* p) { return p; }Example:
Point2w* ptw2 = new(arena) Point2w;Can be converted to:
Point2w* ptw2 = (Point2w*) arena;
ptw2->~Point2w();
if(ptw2 != 0) ptw2->Point2w::Point2w();Generally, Placement Operator new does not support polymorphism; if the derived class is much larger than the base class, the derived class constructor will cause serious destruction;
Constructors are special methods that are automatically called whenever an object of a class is created.
There are 4 types of constructors in C++:
- Default Constructor
- Parameterized Constructor
- Copy Constructor
- Move Constructor
A default constructor is automatically created by the compiler if no constructor is defined. It takes no arguments and initializes members with default values, and it is not generated if the programmer defines any constructor.
class A {};Notice:
-
Any class that does not define a default constructor, the compiler does not necessarily synthesize a default constructor; only when it deems you need one will it synthesize one for you;
-
The default constructor synthesized by the compiler may not explicitly assign default values to each data member;
-
The following cases will result in a default constructor:
- Explicitly initialize an object;
- When an object is passed as a parameter to a function;
- When a function returns a non-reference class object;
A parameterized constructor lets us pass arguments to initialize an object's members. It is created by adding parameters to the constructor and using them to set the values of the data members.
class A
{
public:
int val;
A(int x) : val{x} {}; // Parameterized Constructor
}Notice:
- If a parameterized constructor is defined, the non-parameterized constructor should also be defined as the compiler does not create the default constructor.
A copy constructor is a member function that initializes an object using another object of the same class. Copy constructor takes a reference to an object of the same class as an argument.
class A
{
public:
int val;
A(A& a) { val = a.val; };
};Notice:
-
If a class does not provide a copy constructor, the class internally uses
default memberwise initializationto perform the copy construction. -
The following cases do not exhibit
bitwise copy semantics:- The class contains a member class object with a copy constructor;
- Base class has a copy constructor;
- The class has virtual functions;
- Virtual base class exists (has direct virtual base class or virtual base class in inheritance chain).
-
If no copy or move constructor is defined, the compiler automatically creates an implicit copy constructor, unlike the default constructor which is removed when any constructor is defined.
A move constructor is a special constructor in C++ that creates an object by transferring resources from another object instead of copying them. It uses move semantics (often with std::move) to take ownership of memory or handles from a temporary object, avoiding extra copies and improving performance.
class MyClass
{
public:
int val;
MyClass(int&& x) : val(std::move(x)) {}
}If a constructor of a class has one or more default parameter values, for example:
class complex
{
complex(double = 0.0, double = 0.0);
}Then when we write complex array[10];, the compiler ultimately needs to call:
vec_nex(&array, sizeof(complex), 10, &complex::complex, 0);An explicit constructor in C++ is a constructor declared with the explicit keyword. It prevents the compiler from using that constructor for implicit conversions or copy-initialization.
class A { public: A(long x){}; };
class B { public: explicit B(long x){}; };
A a = x; // ⚠️Warning: implicit conversion: int -> long -> A
B b = x; // ❌Error: no viable conversion from 'int' to 'B'Notice:
- Mark single-argument constructors as
explicit(prevents unexpected implicit conversions).
class A
{
public:
int val;
A() : A(0) {}; // Delegates To Parameterized (one constructor can call another)
A(int a) : val{a} {}
};The following cases must use a member initialization list (initialization list):
- When initializing a reference member;
- When initializing a const member;
- When calling a base class constructor that has a set of parameters;
- When calling a member class constructor that has a set of parameters.
Notice:
- The order of items in the list is determined by the declaration order of members in the class, not by the sorted order in the
initialization list.
A destructor is a special member function, prefixed wiht ~, thsi is automatically called when an object goes out of scope or is destroyed to free resources like memory, files, or connections.
Destructors are automatically present in every C++ class but we can also redefine them using the following syntax:
~ClassName() {};Destructors are called when:
- The function ends.
- The program ends.
- When a block containing local variables ends.
- A delete operator is called.
Destructor Usages:
-
If we don't write a destructor, the compiler provides a default one.
-
The default destructor works fine for classes without dynamic memory or pointers.
-
If a class has pointers or dynamically allocated memory, we must write a destructor.
-
A user-defined destructor releases memory or other resources before the object is destroyed.
-
Writing a destructor in such cases prevents memory leaks.
The destructors of base classes and members are called in the reverse order of the completion of their constructor:
- The destructor for a class object is called before destructors for the members and bases are called.
- Destructors for nonstatic members are called before destructors for base classes are called.
- Destructors for nonvirtual base classes are called before destructors for virtual base classes are called.
struct VirtualBase
{
VirtualBase() { std::cout << "VirtualBase constructor\n"; }
~VirtualBase() { std::cout << "VirtualBase destructor\n"; }
};
struct NonVirtualBase1
{
NonVirtualBase1() { std::cout << "NonVirtualBase1 constructor\n"; }
~NonVirtualBase1() { std::cout << "NonVirtualBase1 destructor\n"; }
};
struct NonVirtualBase2
{
NonVirtualBase2() { std::cout << "NonVirtualBase2 constructor\n"; }
~NonVirtualBase2() { std::cout << "NonVirtualBase2 destructor\n"; }
};
struct Member1
{
Member1() { std::cout << "Member1 constructor\n"; }
~Member1() { std::cout << "Member1 destructor\n"; }
};
struct Member2
{
Member2() { std::cout << "Member2 constructor\n"; }
~Member2() { std::cout << "Member2 destructor\n"; }
};
class Derived : public NonVirtualBase1, public NonVirtualBase2, public virtual VirtualBase
{
private:
Member1 m1;
Member2 m2;
public:
Derived() : NonVirtualBase1(), NonVirtualBase2(), VirtualBase(), m1(), m2()
{
std::cout << "Derived constructor\n";
}
~Derived()
{
std::cout << "Derived destructor\n";
}
};
// VirtualBase constructor
// ↓
// NonVirtualBase1 constructor
// ↓
// NonVirtualBase2 constructor
// ↓
// Member1 constructor
// ↓
// Member2 constructor
// ↓
// Derived constructor
Derived d;
// Derived destructor
// ↓
// Member2 destructor
// ↓
// Member1 destructor
// ↓
// NonVirtualBase2 destructor
// ↓
// NonVirtualBase1 destructor
// ↓
// VirtualBase destructorCreating immortal objects by using deleted destructor:
class Immortal { public: ~Immortal() = delete; };
Immortal obj; // ❌ Error: destructor deleted
Immortal* ptr = new Immortal();
delete ptr; // ❌ Error: destructor deleted – memory leak!A virtual destructor is a destructor declared with the virtual keyword. It ensures that when you delete a derived class object through a base class pointer, the correct destructor (starting from the derived class all they way up to the base class) gets called.
class Base {public: virtual ~Base() {std::cout << "Base Destructor\n";} };
class Derived : public Base
{
int* data;
public:
~Derived() {std::cout << "Derived Destructor\n";}
};
Base* ptr = new Derived();
delete ptr;
// Derived Destructor
// Base DestructorNotice:
- If your class has any virtual functions, it needs a virtual destructor;
- If your class is designed as a base class (even without virtual functions), make destructor virtual;
- If your class is final(not meant for inheritance), a non-virtual destructor is fine.
Private Destructor can prevents the destruction of an object, we ususlly use it to control the destruction of objects of a class:
class Test;
void destroy_test(Test* ptr);
class Test
{
private:
~Test() { std::cout << "Test Destruction\n"; }
public:
friend void destroy_test(Test* t);
};
// Only this function can destruct objects of Test
void destroy_test(Test* ptr)
{
delete ptr;
};
Test t; // ❌ Error: the local variable 't' cannot be destructed because the destructor is private.
Test* t; // ✅ OK: pointer can be declared
delete t; // ❌ Error: cannot delete pointer to Test because destructor is private – memory leak!
Test* t = (Test*)malloc(sizeof(Test)); // ✅ OK: memory allocated, but no destructor called
free(t); // ✅ OK: memory freed, but no destructor called – no output
Test* t = new Test(); // ✅ OK: memory allocated, but destructor is private
destroy_test(t); // ✅ OK: destructor called through friend function, output: "Test-
Destructors cannot be overloaded, a class has exactly one destructor
class hello { public: ~hello() {}; ~hello(int a) {}; // ❌ WRONG - destructor cannot have any parameters int ~hello() {}; // ❌ WRONG - destructor cannot have a return type };
-
If a class has any virtual functions, its destructor must be declared virtual
class Base { // ❌ WRONG – Non-virtual destructor }; class Derived : public Base { int* data; public: Derived() : data(new int[100]) {} ~Derived() { delete[] data; // ❌This never called! memory leak!!! } };
-
Never throw Exceptions from Destructors (Please always mark destructors
noexceptand never throw from them.)class Dangerous { public: ~Dangerous() { throw std::runtime_error("Exception in destructor!"); // ⚠️ Warning, never throw exception from destructor } }; try { Dangerous d; } catch(...) { // This will NOT catch the exception thrown from the destructor, and will call std::terminate() instead! }
class Safe { public: ~Safe() noexcept // ✅ Marked as noexcept to prevent exceptions from propagating { try { // Code that might throw an exception } catch(...) { std::cerr << "Caught exception in Safe destructor, but won't propagate!\n"; } } };
-
Pure virtual Destructor MUST have a body
class Abstract { public: virtual ~Abstract() = 0; // Pure virtual destructor }; // ❌ MUST provide a body!!! // Abstract::~Abstract() // { // std::cout << "Abstract destructor\n"; // } class Concrete : public Abstract { public: ~Concrete() override { std::cout << "Concrete destructor\n"; } };
-
To destroy an object created with the placement new operator, you can explicitly call the object's destructor
class A { public: A() { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } }; char* p = new char[sizeof(A)]; A* ap = new (p) A; ap->A::~A(); delete [] p;
Type conversion relationship between derived and base classes:
graph LR
BaseClass --illegal conversion/assignment--> DerivedClass
DerivedClass --legal conversion/assignment--> BaseClass
Example, memory layout of multiple inheritance:
class Base1 {
public:
Base1();
virtual ~Base1();
virtual void speakClearly();
virtual Base1 *clone() const;
protected:
float data_Base1;
};
class Base2 {
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base2 *clone() const;
protected:
float data_Base2;
};
class Derived : public Base1, public Base2 {
public:
Derived();
virtual ~Derived();
virtual Derived *clone() const;
protected:
float data_Derived;
};The Diamond Problem occurs in multiple inheritance when a derived class inherits from two classes that both inherit from the same base class, creating an ambiguous "diamond" shape in the inheritance hierarchy.
For Example:
class Base
{
public:
int value = 10;
void function() { std::cout << "Base function\n"; }
};
class Left : public Base
{
public:
void leftOnly() {}
};
class Right : public Base
{
public:
void rightOnly() {}
};
class Derived : public Left, public Right
{
// Derived now has TWO copies of Base
// One through Left, one through Right
};
Derived d;
d.value; // ❌ ERROR: Ambiguous
d.function(); // ❌ ERROR: Ambiguous
d.Left::value; // OK - access Left's Base
d.Right::value; // OK - access Right's Base
d.Left::function(); // OK
d.Right::function(); // OKThe Solution:
-
Virtual Inheritance
class Base { public: int value = 10; void function() { std::cout << "Base function\n"; } }; class Left : virtual public Base { public: void leftOnly() {} }; class Right : virtual public Base { public: void rightOnly() {} }; class Derived : public Left, public Right { // Now only ONE copy of Base exists }; Derived d; d.value; // ✅ d.function(); // ✅ d.Left::value; // OK - access Left's Base d.Right::value; // OK - access Right's Base d.Left::function(); // OK d.Right::function(); // OK
Virtual Inheritance is a specialized form of inheritance that solves the [diamond problem](#The Diamond Problem) - ensuring that a base class appears only once in an inheritance hierarchy, even when derived from multiple paths.
Usage:
-
You have a genuine diamond hierarchy
class Animal {}; class Mammal : virtual public Animal {}; class Bird : virtual public Animal {}; class Platypus : public Mammal, public Bird {}; // Needs one Animal
-
Creating interfaces that will be combined
class Drawable { virtual void draw() = 0; }; class Clickable { virtual void onClick() = 0; }; class Button : virtual public Drawable, virtual public Clickable {};
Avoid virtual inheritance when:
- No diamond exists (unnecessary overhead)
- Performance critical (virtual inheritance adds overhead)
- You can use composition istead
Summary:
| Aspect | Without Virtual | With Virtual |
|---|---|---|
| Number of base copies | Multiple (one per path) | Single (shared) |
| Member access | Ambiguous - must qualify | Unambiguous |
| Memory size | Larger (multiple copies) | Smaller (single copy) |
| Performance | Faster (direct access) | Slower (indirection) |
| Constructor complexity | Simple | Complex (most derived initializes) |
| Use case | Simple multiple inheritance | Diamond inheritance |
Notice:
-
Virtual Bases Are Constructed First:
- Virtual base classes (in declaration, depth-first);
- Non-virtual base classes (in declaration order);
- Member objects (in declaration order);
- Derived class constructor body.
class Animal { public: Animal() { std::cout << "Animal constructor\n"; } ~Animal() { std::cout << "Animal destructor\n"; } }; class Mammal : virtual public Animal { public: Mammal() { std::cout << "Mammal constructor\n"; } ~Mammal() { std::cout << "Mammal destructor\n"; } }; class Bird : virtual public Animal { public: Bird() { std::cout << "Bird constructor\n"; } ~Bird() { std::cout << "Bird destructor\n"; } }; class Bat : public Mammal, public Bird { public: Bat() { std::cout << "Bat constructor\n"; } ~Bat() { std::cout << "Bat destructor\n"; } }; // Animal constructor // Mammal constructor // Bird constructor // Bat constructor Bat b; // Bat destructor // Bird destructor // Mammal destructor // Animal destructor
-
Most Derived Class Initializes Virtual Base
class VirtualBase { public: VirtualBase(int x) { std::cout << "VirtualBase: " << x << "\n"; } }; class Intermediate : virtual public VirtualBase { public: // ⚠️ This constructor will be IGNORED for virtual base initialization! Intermediate() : VirtualBase(10) { } }; class Derived : public Intermediate { public: // ✅ Derived MUST initialize VirtualBase directly Derived() : VirtualBase(20), Intermediate() { } }; Derived d; // Output: VirtualBase: 20 (not 10!)
-
Avoid
static_castwith Virtual Inheritanceclass A { public: virtual ~A() {} }; class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; D d; A* a = &d; // ✅ OK B* b = &d; // ✅ OK C* c = &d; // ✅ OK // ⚠️ Static cast may not work correctly with virtual bases A* a2 = static_cast<A*>(b); // Might need offset adjustment // Use dynamic_cast for safe down/up casting in hierarchies with virtual inheritance
-
Virtual Inheritance adds indirection, use only when necessary
class Regular { int data; }; class RegularDerived : public Regular { int more; }; // Access to Regular::data: direct (fast) class VirtualBase { int data; }; class VirtualDerived : virtual public VirtualBase { int more; }; // Access to VirtualBase::data: indirect through pointer (slower) sizeof(Regular); // 4 byte (data) sizeof(RegularDerived); // 8 byte (data + more) sizeof(VirtualBase); // 4 byte (data) sizeof(VirtualDerived); // 16 byte (data + more + vptr)
-
A class can be both virtual and non-virtually inherited, but it creates separate instances
class A {}; class B : virtual public A {}; class C : public A {}; // Non-virtual class D : public B, public C {}; // D has TWO A's: one shared (via B), one separate (via C) D d; // d (B subobject + vptr(pointer to virtual A) + C subobject)
-
Constructors of virtual base classes CAN have parameters, but they MUST be explicitly called from the most derived class constructor
class Base { public: Base(int x) {} }; class Mid : virtual public Base { public: Mid() : Base(0) {} }; // ⚠️Ignored! class Derived : public Mid { public: Derived() : Base(42), Mid() {} // Must initialize Base };
C++ supports polymorphism through the following mechanisms:
- Through a set of implicit conversion operations;
- Through the virtual function mechanism;
- Through dynamic_cast and typeid operators.
Virtual functions are a cornerstone of polymorphism in C++.
Default arguments of virtual functions are statically bound:
#include <iostream>
using namespace std;
class Base {
public:
virtual void fun(int x = 0) { cout << "Base::fun(), x = " << x << endl; }
};
class Derived : public Base {
public:
virtual void fun(int x) { cout << "Derived::fun(), x = " << x << endl; }
};
int main(void) {
Derived d1;
Base* bp = &d1;
bp->fun();
return 0;
}Output:
Derived::fun(), x = 0virtual func() = 0;Purpose:
- Requires derived classes to provide implementations.
- Makes the base class abstract and non-instantiable.
An abstract class can have constructors and destructors:
class Base {
protected:
int x;
public:
virtual void fun() = 0;
Base(int i) { x = i; }
virtual ~Base() = 0;
};
Base::~Base() { cout << "~Base()" << endl; }
class Derived : public Base {
int y;
public:
Derived(int i, int j) : Base(i) { y = j; }
~Derived() { cout << "~Derived()" << endl; }
void fun() { cout << "x = " << x << ", y = " << y << endl; }
};Notice:
- Calling virtual functions from constructors/destructors is valid syntax but generally discouraged.
- These cannot be virtual:
- constructors
- static member functions
- friend functions
- non-member ordinary functions
- If deleting derived objects through base pointers is possible, the base destructor should be virtual.
- Virtual functions can be private; access rules still apply.
Function Overriding in C++ is a type of polymorphism where a derived class refines a function from its base class using the same name, return type, and parameters (i.e., the same function signature).
Conditions:
- The base class function must be
virtual. - The derived class must use the same signature.
- It's a form of runtime polymorphism (dynamic binding).
Example:
struct Base
{
virtual void hello(int g) = 0;
virtual void world() const {};
void print() {};
void say() {};
virtual void fun() {};
};
struct DerivedMid : public Base
{
};
struct DerivedTop : public DerivedMid
{
void hello(double g) override {}; // ❌ ERROR: parameter mismatch
void world() override {}; // ❌ ERROR: cv-qualifier mismatch
void print() override {}; // ❌ ERROR: base function not virtual
void say() {}; // ⚠️ It's not function overriding, it's function hidding
void fun() override {}; // ✅
};Notice:
-
If the base function is not
virtual, the derived function with the same name just hides the base function. This is called function hiding, not overridingstruct Base { void say() {}; virtual void fun() {}; }; struct Derived : public Base { void say() {}; // ⚠️ It's not function overriding, it's function hidding void fun() override {}; // ✅ };
-
Function Overriding slightly slower than normal function calls due to virtual table lookup
// environment: Macos aarch64 #include <chrono> struct Base { virtual void fun(int i) { i += 1; }; }; struct Derived : public Base { __attribute__((noinline)) void say(int i) { i += 1; }; // AVOID compiler inline __attribute__((noinline)) void fun(int i) override { i += 1; }; // AVOID compiler inline }; Derived d; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < 1000000000; ++i) d.say(i); // Non-virtual function call (1931ms) auto end = std::chrono::high_resolution_clock::now(); start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < 1000000000; ++i) d.fun(i); // Virtual function call (2150ms) end = std::chrono::high_resolution_clock::now();
-
Function Overriding could adds memory overhead (vtable, vptr)
struct Base1 { virtual void fun(int i) { i += 1; }; }; struct Base2 { void fun(int i) { i += 1; }; }; struct Derived1 : public Base1 { void fun(int i) override { i += 1; }; }; struct Derived2 : public Base2 { void fun(int i) { i += 1; }; }; sizeof(Base1); // 8 byte (virtual table pointer) sizeof(Base2); // 1 byte (empty class) sizeof(Derived1); // 8 byte (inherits virtual table pointer from Base1) sizeof(Derived2); // 1 byte
-
Function Overriding could increases complexity in large inheritance hierarchies.
[Top](#C++ Object Oriented Programming)
In C++, upcasting is the process of converting a pointer or reference of a derived class to its base class type.
Upcast (casting from derived to base) is generally safe and implicit, but there are specific unsafe conditions:
| Scenario | Safety | Method | Notice |
|---|---|---|---|
| Single inheritance, pointer/reference | ✅ Safe | Implicit conversion | Implicit conversion works. |
| Single inheritance, by value | ❌ Slicing | Implicit conversion (dangerous) | Use pointer/reference instead |
| Multiple inheritance, ambiguous | ❌ Unsafe | Explicit cast required (static_cast or C-style) |
Must specify path |
| Virtual inheritance | Implicit conversion | Compiler handles, but complex | |
| Cross-cast (siblings) | ❌ Unsafe | Not directly possible | Must go through derived |
| Null pointer | ✅ Safe | Implicit conversion or dynamic_cast |
Remains null |
| Const violation | ❌ Unsafe | Implicit conversion | Use const pointer |
| Dangling object | ❌ Unsafe | Any method | Upcast doesn't extend lifetime |
| Incorrect static_cast | ❌ UB | static_cast |
Use dynamic_cast for safety |
-
Single Inheritance
class Base {}; class Derived : public Base {}; // ✅ Usually safe - Derived has all Base parts Derived d; Base* b = &d; // Safe upcast
-
Multiple Inheritance - Ambiguous Base
class A { int a; } class B { int b; } class C : public A, public B {} C* c = new C(); A* ptr1 = c; // ❌Error: ambiguous conversion A* ptr2 = static_cast<A*>(c); // ⚠️OK, but still ambiguous without qualification
-
Virtual Inheritance - Incorrect Offset
class VBase { int data; }; class D1 : virtual public VBase {}; class D2 : virtual public VBase {}; class Final : public D1, public D2 {}; Final f; VBase* vptr = &f; // ✅ Safe, but pointer adjustment needed // ❌ Unsafe - Manual pointer arithmetic bypasses offset calculation VBase* unsafe = (VBase*)((char*)&f + offset); // Don't do this!
-
Cross-Cast in Multiple Inheritance
class X { int x; }; class Y { int y; }; class Z : public X, public Y {}; Z z; X* xptr = &z; Y* yptr = &z; // ❌ Unsafe - Cannot cast between siblings Y* bad = static_cast<Y*>(xptr); // Error: not related Y* bad2 = (Y*)xptr; // ⚠️, Compiles but DANGEROUS! // ✅ Safe way - go through derived Y* safe = static_cast<Y*>(static_cast<Z*>(xptr));
-
Null pointer
Derived* dptr = nullptr; Base* bptr = dptr; // ✅ Safe - still null // But dangerous if you don't check if (bptr) { bptr->baseMethod(); // will not execute(null check saves you) } // However, casting null in complex hierarchies: Derived* dptr2 = nullptr; Base* bptr2 = dynamic_cast<Base*>(dptr2); // ✅ Safe, returns null
-
Const - Correctness Violation
class Base { int x; }; class Derived : public Base {}; const Derived cd; // ❌ Unsafe - casting away const Base* b = const_cast<Base*>(&cd); // Dangerous if Base modifies // ✅ Use const pointer const Base* cb = &cd; // Safe
-
Dangling object
Base* dangerous() { Derived d; // Local object return &d; // ❌ Returns pointer to destroyed object } // d destroyed here - upcast doesn't save you! Base* b = dangerous(); // b is dangling pointer - UNSAFE // ✅ Safe version Base* safe() { Derived* d = new Derived(); return d; // Ownership transferred }
Downcasting is the process of casting a base class pointer/reference to a derived class pointer/reference. It's the opposite of upcasting (which is safe and implicit).
Downcast (casting from base to derived) is generally safe and implicit, but there are specific unsafe conditions:
| Scenario | Safety | Method | Notice |
|---|---|---|---|
| Single inheritance, pointer, type known | static_cast |
Safe ONLY if you're 100% certain of actual type | |
| Single inheritance, pointer, type unknown | ✅ Safe | dynamic_cast |
Returns nullptr if type mismatch, always check result |
| Single inheritance, reference | ✅ Safe | dynamic_cast |
Throws std::bad_cast on failure, catch to handle |
| Single inheritance, by value | ❌ Impossible | N/A | Cannot downcast values - only pointers/references |
| Multiple inheritance, simple downcast | ✅ Safe | dynamic_cast |
Handles pointer adjustments automatically |
| Multiple inheritance, cross-cast | ✅ Safe | dynamic_cast |
Cast between siblings, returns nullptr if not related |
| Virtual inheritance | ✅ Safe | dynamic_cast |
Required for virtual bases, handles offset correctly |
| Non-polymorphic types (no virtual) | ❌ Unsafe | static_cast |
dynamic_cast won't compile; no RTTI available |
| Null pointer | ✅ Safe | Any cast | Casting null remains null (check before use) |
| Const violation | ❌ Unsafe | const_cast + downcast |
Don't cast away constness; use const pointer instead |
| Dangling object | ❌ Unsafe | Any cast | Cast doesn't extend lifetime; object already destroyed |
Incorrect static_cast |
❌ UB | static_cast |
Undefined behavior if type mismatch |
Incorrect dynamic_cast |
✅ Safe | dynamic_cast |
Returns nullptr (pointer) or throws (reference) |
| Downcast then delete | Both | Base destructor must be virtual for safe deletion | |
| Private inheritance | ❌ Unsafe | Any cast | Derived class not accessible through Base interface |
-
Single Inheritance - Pointer
class Base { public: virtual ~Base() = default; }; class Derived : public Base { public: void hello() { }; }; Derived d; Base* b = &d; Derived* d1 = static_cast<Derived*>(b); // ✅ Safe Base* b_null = new Base(); Derived* d2 = static_cast<Derived*>(b_null); // ⚠️ Undefined Behavior! (type unknown) d2->hello(); // ⚠️ May crash or print garbage! (undefined behavior) Derived* d3 = dynamic_cast<Derived*>(b_null); // ✅ Safe (returns nullptr if not Derived) if (d3) { d3->hello(); }
-
Single Inheritance - Reference
class Base { public: virtual ~Base() = default; }; class Derived : public Base { public: void hello() { }; }; Derived d; Base& b = d; try { Derived& d = dynamic_cast<Derived&>(b); d.hello(); // ✅ Safe } catch (const std::bad_cast& e) {}
-
Multiple Inheritance
class Base1 { public: virtual ~Base1() = default; }; class Base2 { public: virtual ~Base2() = default; }; class Derived : public Base1, public Base2 {}; Base1* b1 = new Derived(); Base2* b2 = dynamic_cast<Base2*>(b1); // ✅ Safe cross-cast Base2* b3 = static_cast<Base2*>(b1); // ❌ would be unsafe - wrong offset!
-
Virtual Inheritance
class VBase { public: virtual ~VBase() = default; }; class D1 : virtual public VBase {}; class D2 : virtual public VBase {}; class Final : public D1, public D2 {}; Final f; VBase* vb = &f; D1* d1 = dynamic_cast<D1*>(vb); // ✅ Safe - dynamic_cast required D1* d1_bad = static_cast<D1*>(vb); // ❌ Compiler error or wrong offset
-
Non-polymorphic Types
class Base {}; // Non-Polymorphic(no virtual functions) class Derived : public Base {}; Base* b = new Derived(); Derived* d1 = dynamic_cast<Derived*>(b); // ❌ Compiler error! Derived* d2 = static_cast<Derived*>(b); // ⚠️ Compiler ok, but unsafe
-
Const Correctness
class Base { public: virtual ~Base() = default; }; class Derived : public Base {}; const Base* cb = new Derived(); Derived* d = dynamic_cast<Derived*>(cb); // ❌ Compiler error! const Derived* cd = dynamic_cast<const Derived*>(cb); // ✅ OK
-
Dangling Object
class Base { public: virtual ~Base() = default; }; class Derived : public Base { public: void hello() {}; }; Base* gen_dangling() { Derived d; return &d; // Returns pointer to local object } // d destroyed here Base* b = gen_dangling(); Derived* d = dynamic_cast<Derived*>(b); // ⚠️ Compiler succeeds but object dead! d->hello(); // ❌ UB - object already destroyed
-
Downcast and Delete
class Base { public: virtual ~Base() = default; }; // ✅ Virtual destructor class Derived : public Base {}; class BadBase { public: ~BadBase(){}; }; // non-virtual class BadDerived : public BadBase {}; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); delete d; // ✅ Safe - virtual destructor calls Derived::~Derived() BadBase* bb = new BadDerived(); delete bb; // ❌ UB - Derived destructor not called
static_cast performs compile-time type conversion and is mainly used for explicit conversions that are considered safe by the compiler.
Usage:
-
Numeric Type Conversions
float pi =3.14; int crounded = (int)pi; // NOT recommend!!! int rounded = static_cast<int>(pi); // recommend
-
Pointer Conversions in Class Hierarchies
class base{}; class derived : public base { void print() { std::cout << "hello" << std::endl; } }; // Upcasting: always safe derived* derived_obj = new derived(); base* base_obj = static_cast<base*>(derived_obj); // Downcasting: no runtime check base* base_obj = new base(); derived* derived_obj = static_cast<derived*>(base_obj); // ⚠️ Compiles but DANGEROUS!
-
Enum Conversions
enum Color { RED, GREEN, BLUE }; enum class Status { OK, ERROR, PENDING }; int r = RED; // ✅ 0 - implicit (allowed but not recommended) int g = static_cast<int>(GREEN); // ✅ 1 - explicit and clear Color green = static_cast<Color>(1); // ✅ int s = Status::OK; // ❌ No implicit conversion! int ok_value = static_cast<int>(Status::OK); // ✅ 0 Status OK = static_cast<Status>(0); // ✅ Must use static_cast
Notice:
-
Not allowed casting between unrelated types (e.g.,
int*->float*). -
Compared to C-style cast,
static_castprovides:-
compile-time type safety
class Base { }; class Derived1 : public Base {}; class Derived2 : public Base {}; Derived1* d1 = new Derived1(); Base* b = d1; Derived2* d2 = (Derived2*)b; // ⚠️ COMPILES but is WRONG! Derived2* d2 = static_cast<Derived2*>(b); // ❌ Compiler error!
-
code clarity
int x = 42; const int* ptr = &x; int* p1 = (int*)ptr; // ⚠️ Or const removal? int* p2 = const_cast<int*>(ptr); // ✅ Clearly: const removal
-
reduced risk of unintended conversions
class Base {}; class Derived : public Base { int x; }; int* i = new int(42); Base* b = (Base*)i; // ⚠️ Compiles but complete nonsense! Base* b = static_cast<Base*>(i); // ❌ Compiler error - unrelated types
-
better error messages
-
works well with templates
template<typename T, typename U> T dangerous_convert(U u) { return (T)u; // ❌ Does ANY conversion without checking } template<typename T, typename U> T safe_convert(U u) { return static_cast<T>(u); // ✅ Fails at compile time if conversion invalid }
-
dynamic_cast is a cast operator that converts data from one type to another type at runtime. It is mainly used in inherited class hierarchies for safely casting the base class pointer or reference to a derived class (called downcasting).
Usage:
-
Safe Downcasting
class Base{ virtual void say() {std::cout << "Base";} }; class Derived : public Base { void say() {std::cout << "Derived";} void work() {std::cout << "Derived work";} }; Base* ptr = new Base(); Derived* derived_ptr = dynamic_cast<Derived*>(ptr); // ✅ Safe if (derived_ptr) derived_ptr->work(); else derived_ptr->say();
-
Reference Downcasting
class Base{ virtual void say() {std::cout << "Base";} }; class Derived : public Base { void say() {std::cout << "Derived";} void work() {std::cout << "Derived work";} }; Base base{}; try{ Derived& derived_ref = dynamic_cast<Derived&>(base); // ✅ Safe derived_ref.work(); } catch (const std::bad_cast& e) { base.say(); }
-
Cross-Casting in Multiple Inheritance
class A { virtual void say() {std::cout << "A";} }; class B { virtual void say() {std::cout << "B";} }; class C : public A, public B {}; C c; A* a = &c; B* b = dynamic_cast<B*>(a); // ✅ Safe
Notice:
-
dynamic_castworks only for polymorphic types (withvirtualfunctions)class Base{}; class Derived : public Base{}; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ❌ error, Base is not polymorphic
dynamic_castrelies on RTTI(Run-Time Type Information), which is only generated for polymorphic types:-
Virtual function (any)
class Base{ public: virtual void func() {} // ✅ Polymorphic }; class Derived : public Base {}; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ✅
-
Virtual destructor (most common for base classes)
class Base { public: virtual ~Base() {} // ✅ Polymorphic }; class Derived : public Base {}; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ✅
-
Pure virtual function
class Base{ public: virtual void func() = 0; // ✅ Polymorphic }; class Derived : public Base{ public: void func() override {} // ✅ Implementing the pure virtual function }; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ✅
-
Overriding virtual function (inherited polymorphism)
class Base { public: virtual void func() {} }; class Derived : public Base { // ✅ Polymorphic (inherits from Base) void func() override {} }; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ✅
-
Inherits from polymorphic class
class Base { public: virtual ~Base() {} }; class Derived : public Base { // ✅ Polymorphic (inherits virtual destructor) }; Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // ✅
-
-
Returns
nullptrfor pointers if thedynamic_castfails. -
Throws
std::bad_castfor references if thedynamic_castfails. -
dynamic_casthas runtime overhead (vtbl lookup); do not use it in performance-critical conditions. -
When you already know the type, use
static_castinstead:class Base{}; class Derived : public Base{}; Derived derived; Base* base = &derived; Derived* derived1 = dynamic_cast<Derived*>(base); // ❌, Overkill Derived* derived2 = static_cast<Derived*>(base); // ✅, Faster
-
Prefer virtual functions when possible
class Good { public: virtual void doSomething() = 0; // Better than dynamic_cast };
reinterpret_cast is a type of casting operator used in C++. It is used to convert a pointer of some data type into a pointer of another data type, even if the data types before and after conversion are different.
Usage:
-
Serialization (when you know what you're doing)
struct Packet { int id; char data[64]; }; Packet packet; char* buffer = reinterpret_cast<char*>(&packet);
-
MMIO / embedded systems
volatile uint32_t* reg = reinterpret_cast<volatile uint32_t*>(0x40021000);
-
Type punning (treating the same memory as different types)
int i = 0x3F800000; float* f = reinterpret_cast<float*>(&i); std::cout << *f; // ⚠️ Undefined behavior - violates strict aliasing rule
Notice:
- It does not check if the pointer type and the data pointer are the same or not.
- Dereferencing the result of a
reinterpret_castfor unrelated types causes undefined behavior (strict aliasing violation). - Casting between function and data pointers causes undefined behavior (except on some platforms).
| Feature | static_cast |
dynamic_cast |
reinterpret_cast |
|---|---|---|---|
| When checked | Compile time | Runtime (RTTI) | Compile time |
| Safety | Moderate | Safe (returns nullptr on failure) | Very dangerous |
| Performance | Zero overhead | Overhead (vtable lookup) | Zero overhead |
| Required conditions | Related types | Polymorphic types (virtual functions) | Anything (blind conversion) |
| Failure behavior (ptr) | Undefined behavior | Returns nullptr |
Undefined behavior |
| Failure behavior (ref) | Undefined behavior | Throws std::bad_cast |
Undefined behavior |
| Use case | Safe, well-defined conversions | Safe downcasting in hierarchies | Low-level bit manipulation |
[Top](#C++ Object Oriented Programming)
NRV (Named Return Value) optimization converts pass-by-value return functions to pass-by-reference return functions;
Example:
X bar()
{
X xx;
// ...process xx
return xx;
}Converted to:
void
bar( X &__result )
{
// default constructor is called
// c++ pseudo-code
__result.X::X();
// ...directly process __result
return;
}The destruction of temporary objects occurs at the last step in the process of evaluating a full-expression; however, the following cases are exceptions:
-
Temporary objects with expression evaluation results should be saved until the object's initialization is complete;
string proNameVersion = !verbose ? 0 : proName + progVersion; // the temporary object produced should be destroyed after the ?: expression evaluation, but proNameVersion's initialization needs the temporary object, so it should be preserved until the initialization completes
-
If a temporary object binds to a reference, the object will persist until the lifetime of the reference being initialized ends;
TODO
TODO
[Top](#C++ Object Oriented Programming)
-
new function: allocate memory first, then call constructor; delete function: call destructor first, then release memory;
-
Only
nonstatic data memberis inside the object, everything else is outside; -
Any class that declares
virtual functionwill have a virtual table (multiple inheritance and virtual inheritance may have multiple virtual tables), and the virtual table stores the addresses ofvirtual functionof the class. Each object of this class has a pointer (vptr) pointing to the virtual table. The vptr is assigned duringconstructor. Each class that declares virtual functions has a virtual table, and each of its instances has a pointer pointing to the virtual table. -
Inheritance relationships can also be specified as
virtual(shared), e.g.:class istream : virtual public ios { ... };; in this case, no matter how many times the base class is derived in the inheritance chain, it always exists as only one instance. -
Virtual inheritance affects efficiency.
-
The memory size of a
class object= total size ofnonstatic data member+ space filled due toalignment+ overhead produced byvirtual. -
castcannot change the actual address that a pointer points to, it only affects the "interpretation method" of that address. -
Assigning a
base class objectto aderived class object, initializing it, or using type conversion to convertbase classtoderived classis illegal; -
Assigning a
derived class objectto abase class object, initializing it, or using type conversion to convertderived class objecttobase class objectis allowed; but slicing occurs. -
A
base class object pointercan point to aderived class object; aderived class object pointercannot point to abase class object. This is the fundamental condition for implementing polymorphism. -
Using type conversion to convert
derived class objecttobase class objectwill not cause slicing; -
explicit can prevent a "single-parameter constructor" from being treated as a
conversionoperator. -
When the compiler generates a default constructor, it will not initialize other members in the class.
-
An
empty class objecthas a non-zero size; the compiler inserts acharto allow any two objects of the class to have unique addresses in memory; if there arevirtual function, a vptr is also added to point to thevirtual table; -
Generally, members declared later are in higher positions in the
class object, and for C compatibility,vptris usually placed at the end of theclass object. -
static memberis placed in thedata segmentand not in theclass object. -
The role of
virtual destructoris to make the base class destructor shared, preventing memory leaks. Do not declarevirtual destructoraspure virtual destructor. -
In the following 3 cases, the compiler will call the copy constructor:
-
An object is passed by value into a function body
-
An object is returned by value from a function
-
An object needs to be initialized through another object
-
-
protectedinheritance convertspublicmembers toprotected -
privateinheritance converts public and protected members toprivate -
The three major characteristics of object-oriented programming: polymorphism (Polymorphism), encapsulation (Encapsulation), and inheritance (Inheritance)
-
Polymorphism: the same operation acts on different objects, producing different results. It has the following classifications:
- Compile-time polymorphism (overloading)
- Runtime polymorphism (virtual functions)
-
The differences between malloc and operator new:
- Calling Constructors:
newcalls constructors, whilemallocdoes not. - Operator vs function:
newis an operator, whilemallocis a function. - Return Type:
newreturns exact data type, whilemallocreturns void*. - Failure Condition: on failure,
mallocreturns NULL where asnewthrows bad_alloc exception. - Memory: In case of
new, memory is allocated from free store where as inmallocmemory allocation is done from heap. - Size: Required size of memory is calculated by compiler for
new, where as we have to manually calculate size formalloc. - Buffer Size:
mallocallows to change the size of buffer using realloc whilenewdoesn't.
- Calling Constructors:
[Top](#C++ Object Oriented Programming)
[1] #pragma pack(push) and #pragma pack(pop) and #pragma pack()
[2] First Exploration of C++ CRTP (Curiously Recurring Template Pattern)
[3] malloc vs new

