c++多态面试题之(析构函数与虚函数)
有以下问题展开
- 析构函数要不要定义成虚函数?
- 基类的析构函数要不要定义成虚函数?
- 如果不定义会有什么问题,定义了在什么场景下起作用。
1. 基类析构函数何时必须定义为虚函数?
当且仅当通过基类指针(或引用)删除派生类对象时,基类的析构函数必须是虚函数。
class Base {
public:virtual ~Base() { cout << "Base destructor" << endl; } // 必须为虚函数
};class Derived : public Base {
private:int* data;
public:Derived() { data = new int[10]; }~Derived() override { delete[] data; cout << "Derived destructor" << endl; }
};// 关键代码:
Base* ptr = new Derived(); // 基类指针指向派生类对象
delete ptr; // 如果Base::~Base()不是虚函数,则只调用Base的析构函数
2. 若基类析构函数不是虚函数,会发生什么?
- 内存泄漏:当通过基类指针删除派生类对象时,只会调用基类的析构函数,而派生类的析构函数不会被调用。例如:
class Base {
public:~Base() { cout << "Base::~Base()" << endl; } // 非虚析构函数
};class Derived : public Base {
private:int* data;
public:Derived() { data = new int[10]; }~Derived() { delete[] data; // 资源释放代码cout << "Derived::~Derived()" << endl; }
};Base* ptr = new Derived();
delete ptr; // 只调用Base::~Base(),Derived的析构函数未被调用,data内存泄漏!
-
安全准则:
若一个类可能作为基类,且存在通过基类指针删除派生类对象的场景,必须将基类析构函数定义为虚函数。
3. 虚析构函数的作用机制
多态调用:虚析构函数会触发动态绑定(运行时多态)。当通过基类指针删除对象时,C++ 会根据指针实际指向的对象类型(而非指针类型)来决定调用哪个析构函数。
Base* ptr = new Derived();
delete ptr; // 实际调用Derived::~Derived(),再调用Base::~Base()
-
调用顺序:
派生类析构函数自动调用基类析构函数(无论基类析构函数是否为虚函数),但只有虚析构函数能确保派生类析构函数被先调用。
4. 何时不需要虚析构函数?
不作为基类的类:若一个类不打算被继承(如final
类),其析构函数无需为虚函数。
class NonInheritable final {
public:~NonInheritable() { /* ... */ } // 无需为虚
};
不通过基类指针删除对象:
若基类仅用于继承接口而非管理资源(即不涉及删除delete basePtr
),析构函数可以不是虚函数。
class Interface {
public:virtual void doSomething() = 0;~Interface() { /* 非虚,因为不通过Interface*删除对象 */ }
};
纯虚析构函数:可将基类析构函数声明为纯虚函数,但必须提供定义:
class Base {
public:virtual ~Base() = 0; // 纯虚析构函数
};
Base::~Base() {} // 必须提供定义
场景 | 基类析构函数是否需为虚函数? |
---|---|
通过基类指针删除派生类对象 | 必须为虚函数 |
类不打算被继承 | 无需为虚函数 |
类作为基类但不通过基类指针删除对象 | 无需为虚函数,但建议为虚以避免误用 |
核心原则:若基类有虚函数或可能被继承,永远将其析构函数定义为虚函数。 这是防止内存泄漏的重要实践。