在 C++ 中,将析构函数声明为 virtual 是一种常见的做法,特别是在使用继承的情况下。这么做的主要原因是确保当你删除一个指向派生类对象的基类指针时,能够正确调用派生类的析构函数,从而防止资源泄漏。下面详细解释为什么要在基类中将析构函数声明为 virtual。
基类与派生类
假设有一个基类 CBaseHmi 和一个派生类 CHmi:
class CBaseHmi {
public:
virtual ~CBaseHmi() = default; // 虚析构函数
};
class CHmi : public CBaseHmi {
public:
CHmi();
virtual ~CHmi() = default;//显示 virtual,只要基类加了,这里加不加都是正确调用派生类的析构函数
};
为什么析构函数要声明为 virtual
当一个类有虚函数时(包括虚析构函数),它会在对象中维护一个虚函数表(vtable),这个表包含了类的虚函数指针。当你通过基类指针删除一个对象时,虚函数机制确保调用的是最派生类的析构函数。
示例解释
void example() {
CBaseHmi* obj = new CHmi();
delete obj; // 正确调用 CHmi 的析构函数
}
在这个示例中:
- obj 是一个指向 CBaseHmi 类型的指针,但它实际上指向一个 CHmi 对象。
- 如果 CBaseHmi 的析构函数不是虚函数,那么 delete obj 只会调用 CBaseHmi 的析构函数,而不会调用 CHmi
的析构函数。这可能会导致资源泄漏,因为 CHmi 的析构函数中可能有清理特定资源的代码。
没有虚析构函数的情况下
如果没有虚析构函数,派生类的析构函数不会被调用。例如:
class CBaseHmi {
public:
~CBaseHmi() = default; // 非虚析构函数
};
class CHmi : public CBaseHmi {
public:
CHmi();
~CHmi() { // 派生类析构函数
std::cout << "CHmi destructor" << std::endl;
}
};
void example() {
CBaseHmi* obj = new CHmi();
delete obj; // 只调用 CBaseHmi 的析构函数
}
在这个例子中,输出不会包含 “CHmi destructor”,因为 CHmi 的析构函数没有被调用。
使用虚析构函数
通过将基类的析构函数声明为虚函数,可以确保正确调用派生类的析构函数:
class CBaseHmi {
public:
virtual ~CBaseHmi() = default; // 虚析构函数
};
class CHmi : public CBaseHmi {
public:
CHmi();
~CHmi() { // 派生类析构函数
std::cout << "CHmi destructor" << std::endl;
}
};
void example() {
CBaseHmi* obj = new CHmi();
delete obj; // 正确调用 CHmi 的析构函数
}
在这个例子中,输出将包含 “CHmi destructor”,因为 CHmi 的析构函数被正确调用。
总结
将析构函数声明为 virtual 以确保:
- 当通过基类指针删除对象时,能够正确调用派生类的析构函数。
- 防止资源泄漏和未定义行为。
这是面向对象编程中管理资源和多态性的关键机制之一。