文章目录
- 1、term7:Declare destructors virtual in polymorphic base classes
- 2、总结
- 3、相关面试题
- 3.1 析构函数在什么情况下声明为虚函数
- 4、参考
1、term7:Declare destructors virtual in polymorphic base classes
带有多态性质的基类应该声明一个virtual析构函数;如果class内带有任何virtual函数,它就该拥有一个virtual析构函数;
举个反面栗子:
#include <iostream>
#include <memory> // 基类 TimeKeeper
class TimeKeeper {
public: TimeKeeper() { std::cout << "TimeKeeper constructor called." << std::endl; } ~TimeKeeper() { std::cout << "TimeKeeper destructor called." << std::endl; } // 假设 TimeKeeper 类有其他成员或方法... // ...
}; // 派生类 AtomicClock
class AtomicClock : public TimeKeeper {
public: AtomicClock() { std::cout << "AtomicClock constructor called." << std::endl; } ~AtomicClock() { std::cout << "AtomicClock destructor called." << std::endl; } // 原子钟特有的成员或方法... // ...
}; // 派生类 WaterClock
class WaterClock : public TimeKeeper {
public: WaterClock() { std::cout << "WaterClock constructor called." << std::endl; } ~WaterClock() { std::cout << "WaterClock destructor called." << std::endl; } // 水钟特有的成员或方法... // ...
}; // 派生类 WristWatch
class WristWatch : public TimeKeeper {
public: WristWatch() { std::cout << "WristWatch constructor called." << std::endl; } ~WristWatch() { std::cout << "WristWatch destructor called." << std::endl; } // 腕表特有的成员或方法... // ...
}; // 工厂函数,返回 TimeKeeper 派生类的指针
TimeKeeper* getTimeKeeper() { // 这里我们简单地返回 AtomicClock 的实例, // 但在实际中,可以根据需要返回不同派生类的实例。 return new AtomicClock();
} int main() { // 从 TimeKeeper 继承体系获取一个动态分配的对象 TimeKeeper* ptk = getTimeKeeper(); // 使用 ptk 指向的对象... // ... // 释放内存,避免资源泄漏 delete ptk; return 0;
}
输出内容如下:
TimeKeeper constructor called.
AtomicClock constructor called.
TimeKeeper destructor called.
先来说一下这为什么是一个反面教材,第二个getTimeKeeper()返回的指针指向一个derived对象,而那个对象却被一个base class指针删除。因为析构的顺序,是先析构derived对象,然后再析构base对象。通过基类指针删除子类对象时,仅调用了基类的析构函数,而导致子类的Derived成分没有销毁,产生了一个“局部销毁”对象,这将导致资源泄漏、破坏数据结构、在调试器上浪费时间的严重后果。消除这个问题的做法很简单:给base class内声明一个virtual对象,这样删除对象,就会“如你所愿”。
举个栗子:
#include <iostream>
#include <memory> // 基类 TimeKeeper
class TimeKeeper {
public: TimeKeeper() { std::cout << "TimeKeeper constructor called." << std::endl; } virtual ~TimeKeeper() { std::cout << "TimeKeeper destructor called." << std::endl; } // 假设 TimeKeeper 类有其他成员或方法... // ...
}; // 派生类 AtomicClock
class AtomicClock : public TimeKeeper {
public: AtomicClock() { std::cout << "AtomicClock constructor called." << std::endl; } ~AtomicClock() { std::cout << "AtomicClock destructor called." << std::endl; } // 原子钟特有的成员或方法... // ...
}; // 派生类 WaterClock
class WaterClock : public TimeKeeper {
public: WaterClock() { std::cout << "WaterClock constructor called." << std::endl; } ~WaterClock() { std::cout << "WaterClock destructor called." << std::endl; } // 水钟特有的成员或方法... // ...
}; // 派生类 WristWatch
class WristWatch : public TimeKeeper {
public: WristWatch() { std::cout << "WristWatch constructor called." << std::endl; } ~WristWatch() { std::cout << "WristWatch destructor called." << std::endl; } // 腕表特有的成员或方法... // ...
}; // 工厂函数,返回 TimeKeeper 派生类的指针
TimeKeeper* getTimeKeeper() { // 这里我们简单地返回 AtomicClock 的实例, // 但在实际中,可以根据需要返回不同派生类的实例。 return new AtomicClock();
} int main() { // 从 TimeKeeper 继承体系获取一个动态分配的对象 TimeKeeper* ptk = getTimeKeeper(); // 使用 ptk 指向的对象... // ... // 释放内存,避免资源泄漏 delete ptk; return 0;
}
输出内容如下:
TimeKeeper constructor called.
AtomicClock constructor called.
AtomicClock destructor called.
TimeKeeper destructor called.
virtual函数的目的就是允许derived class的实现得以客制化;任何class只要带有virtual函数几乎确定应该也有一个virtual析构函数。
2、总结
书山有路勤为径,学海无涯苦作舟。
3、相关面试题
3.1 析构函数在什么情况下声明为虚函数
在面向对象的编程中,析构函数应该被声明为虚函数(virtual)当且仅当类被用作基类,并且你打算通过基类指针来删除派生类对象时(只有这么一个情况下)。这是为了防止发生所谓的“切片”问题(slicing problem)和确保正确的对象析构顺序。
当有一个基类指针指向一个派生类对象,并通过这个基类指针来调用 delete 操作符时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类部分的对象没有被正确地销毁,可能造成资源泄漏和其他问题。
通过将基类的析构函数声明为虚函数,可以确保当通过基类指针删除对象时,先调用派生类的析构函数,然后调用基类的析构函数,从而正确地释放所有资源。
4、参考
4.1 《More Effective C++》