#include <iostream>
class A {
public:
A() { std::cout << "A constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
};
class B : virtual public A {
public:
B() { std::cout << "B constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
};
class C : virtual public A {
public:
C() { std::cout << "C constructor" << std::endl; }
~C() { std::cout << "C destructor" << std::endl; }
};
class D : public B, public C {
public:
D() { std::cout << "D constructor" << std::endl; }
~D() { std::cout << "D destructor" << std::endl; }
};
class E : public D {
public:
E() { std::cout << "E constructor" << std::endl; }
~E() { std::cout << "E destructor" << std::endl; }
};
构造函数和析构函数调用顺序注意事项
- 构造函数顺序:
- 首先调用虚基类(这里是
A
)的构造函数,无论虚基类在继承体系中的位置如何,它总是第一个被构造。这确保了虚基类子对象在其他派生类构造之前被正确初始化。
- 然后按照继承列表中出现的顺序调用非虚基类的构造函数,即
B
和C
(在D
类中),最后调用自身(D
或E
)的构造函数。
- 析构函数顺序:
- 析构函数的调用顺序与构造函数相反。首先调用自身(
E
或D
)的析构函数,然后按照继承列表中出现的相反顺序调用非虚基类的析构函数(C
和B
),最后调用虚基类(A
)的析构函数。
避免内存泄漏风险
- 正确管理资源:在构造函数中分配的资源,要在对应的析构函数中正确释放。例如,如果在某个类的构造函数中使用
new
分配了内存,那么在析构函数中要使用delete
释放该内存。
- 遵循RAII原则:使用智能指针(如
std::unique_ptr
、std::shared_ptr
)来管理动态分配的资源。这样可以确保资源在对象生命周期结束时自动释放,避免手动管理内存可能出现的遗漏释放导致的内存泄漏。例如,如果类中持有动态分配的数组,可以使用std::unique_ptr<int[]>
来管理,而不是直接使用int*
。
- 确保虚析构函数:如果基类有虚函数,基类的析构函数应该声明为虚函数。这样在通过基类指针删除派生类对象时,会调用正确的派生类析构函数,从而保证派生类对象的资源被正确释放。在上述例子中,
A
类如果有虚函数,其析构函数应声明为虚函数virtual ~A() { std::cout << "A destructor" << std::endl; }
。