面试题答案
一键面试父类析构函数调用过程分析
- 多重继承情况:在多重继承中,子类对象包含多个父类子对象。当子类对象被销毁时,父类析构函数的调用顺序与它们在子类定义中声明的顺序相同。例如,如果子类
Child
继承自Parent1
、Parent2
和Parent3
,即class Child : public Parent1, public Parent2, public Parent3
,那么析构时先调用Parent3
的析构函数,接着是Parent2
,最后是Parent1
。这是因为在对象构造时,Parent1
先被构造,然后Parent2
,最后Parent3
,析构顺序与之相反。 - 虚拟继承情况:虚拟继承用于解决菱形继承问题,确保从同一虚基类派生的多个子类对象中只存在一份虚基类子对象。当子类对象被销毁时,虚基类的析构函数在其直接派生类的析构函数之前被调用。例如,在菱形继承结构
A -> B -> D
和A -> C -> D
(其中A
是虚基类)中,D
对象析构时,先调用A
的析构函数,然后是C
,接着是B
,最后是D
自身的析构函数。 - 混合情况:当多重继承和虚拟继承混合时,虚基类的析构函数总是在非虚基类的析构函数之前被调用。并且对于非虚基类,按照它们在子类定义中声明的顺序逆序调用析构函数。
确保资源正确释放和避免内存泄漏的方法
- 遵循RAII原则:使用资源获取即初始化(RAII)原则,在对象构造时获取资源,在析构时释放资源。例如,使用智能指针管理动态分配的内存,这样当对象析构时,智能指针会自动释放所管理的内存。
- 正确实现析构函数:每个类的析构函数应该正确释放该类所拥有的资源。对于包含指针成员的类,析构函数中要确保释放这些指针所指向的内存。
- 避免悬空指针:在析构函数中释放指针后,将指针设置为
nullptr
,以避免悬空指针的产生。
代码示例
#include <iostream>
#include <memory>
class VirtualBase {
public:
VirtualBase() { std::cout << "VirtualBase constructor" << std::endl; }
virtual ~VirtualBase() { std::cout << "VirtualBase destructor" << std::endl; }
};
class Base1 : virtual public VirtualBase {
public:
Base1() { std::cout << "Base1 constructor" << std::endl; }
~Base1() { std::cout << "Base1 destructor" << std::endl; }
};
class Base2 : virtual public VirtualBase {
public:
Base2() { std::cout << "Base2 constructor" << std::endl; }
~Base2() { std::cout << "Base2 destructor" << std::endl; }
};
class Derived : public Base1, public Base2 {
public:
std::unique_ptr<int> data;
Derived() : data(std::make_unique<int>(42)) {
std::cout << "Derived constructor" << std::endl;
}
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
{
Derived d;
}
return 0;
}
在上述代码中:
VirtualBase
是虚基类,Base1
和Base2
通过虚拟继承从VirtualBase
派生。Derived
类多重继承自Base1
和Base2
。Derived
类使用std::unique_ptr
管理动态分配的整数,遵循RAII原则。- 当
Derived
对象d
超出作用域时,先调用VirtualBase
的析构函数,然后按照声明顺序逆序调用Base2
、Base1
的析构函数,最后调用Derived
自身的析构函数,在此过程中std::unique_ptr
会自动释放其所管理的内存,避免内存泄漏。