文章目录
- 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++》