这解决了关于为抽象基类声明虚拟析构函数的第二个问题(例如,至少一个成员函数是纯虚拟的)。这是 LLVM clang++ 编译器捕获潜在问题的真实示例。这发生在 Apple Developer 为 Mac OS X Mavericks 操作系统提供的命令行工具版本中。
假设您有一个派生类的集合,这些派生类最终具有带有抽象基类的父类来定义公共接口。然后有必要有一个像向量这样的存储容器,它被有意声明为存储指向每个元素的抽象基类的指针。稍后,遵循良好的工程实践,需要“删除”容器元素并将内存返回到堆中。最简单的方法是逐个元素遍历向量并在每个元素上调用删除操作。
好吧,如果抽象基类没有将析构函数声明为虚拟,clang++ 编译器会给出一个友好的警告,即在抽象类上调用非虚拟析构函数。请记住,实际上只有派生类是使用 operator new 从堆中分配的。继承关系派生的类指针类型确实是抽象基类类型(例如 is-a 关系)。
如果抽象基类析构函数不是虚拟的,那么如何调用正确的派生类的析构函数来释放内存?充其量编译器知道得更好(至少可能对 C++11 有影响),并让它发生。如果启用了 -Wall 编译器选项,那么至少应该出现编译警告。然而,更糟糕的是,派生类的析构函数永远不会到达,内存永远不会返回到堆中。因此,现在存在内存泄漏,可能很难追踪和修复。只需将“虚拟”添加到抽象基类析构函数声明中即可。
示例代码:
class abstractBase
{
public:
abstractBase() { };
~abstractBase() { };
virtual int foo() = 0;
};
class derived : abstractBase
{
public:
derived() { };
~derived() { };
int foo() override { return 42; }
};
//
// Later on, within a file like main.cpp . . .
// (header file includes are assumed to be satisfied)
//
vector<abstractBase*> v;
for (auto i = 0; i < 1000; i++)
{
v.push_back(new derived());
}
//
// do other stuff, logic, what not
//
//
// heap is running low, release memory from vector v above
//
for (auto i = v.begin(); i < v.end(); i++)
{
delete (*i); // problem is right here, how to find the derived class' destructor?
}
要解决这种潜在的内存泄漏,抽象基类必须将其析构函数声明为虚拟。没有其他要求。抽象基类现在变为:
class abstractBase
{
public:
abstractBase() { };
virtual ~abstractBase() { }; // insert virtual right here
virtual int foo() = 0;
}
请注意,抽象基类当前具有空的构造函数和析构函数体。正如 Lightness 上面回答的那样,编译器为抽象基类(如果工程师未定义)创建默认构造函数、析构函数和复制构造函数。强烈建议查看 C++ 创建者 Bjarne Stroustrup 编写的任何 C++ 编程语言版本,以了解有关抽象基类的更多详细信息。