这是构建和销毁类的方式。
首先构造基础,然后派生。所以在Base的构造函数中,还没有创建Derived。因此,它的任何成员函数都不能被调用。所以如果 Base 的构造函数调用了虚函数,它就不可能是 Derived 的实现,它必须是 Base 的实现。但是 Base 中的函数是纯虚函数,没有什么可调用的。
在销毁时,首先销毁 Derived,然后销毁 Base。所以再一次在 Base 的析构函数中没有 Derived 的对象来调用函数,只有 Base。
顺便说一句,只有在函数仍然是纯虚拟的情况下,它才是未定义的。所以这是定义明确的:
struct Base
{
virtual ~Base() { /* calling foo here would be undefined */}
virtual void foo() = 0;
};
struct Derived : public Base
{
~Derived() { foo(); }
virtual void foo() { }
};
讨论已继续提出替代方案:
- 它可能会产生编译器错误,就像尝试创建抽象类的实例一样。
示例代码无疑是这样的: class Base { // other stuff virtual void init() = 0; 虚空清理()= 0;};
Base::Base()
{
init(); // pure virtual function
}
Base::~Base()
{
cleanup(); // which is a pure virtual function. You can't do that! shouts the compiler.
}
很明显,您正在做的事情会给您带来麻烦。一个好的编译器可能会发出警告。
另一种方法是查找Base::init()
and的定义Base::cleanup()
,如果存在则调用它,否则调用链接错误,即将清理视为非虚拟的,以用于构造函数和析构函数。
问题是,如果您有一个调用虚函数的非虚函数,这将不起作用。
class Base
{
void init();
void cleanup();
// other stuff. Assume access given as appropriate in examples
virtual ~Base();
virtual void doinit() = 0;
virtual void docleanup() = 0;
};
Base::Base()
{
init(); // non-virtual function
}
Base::~Base()
{
cleanup();
}
void Base::init()
{
doinit();
}
void Base::cleanup()
{
docleanup();
}
在我看来,这种情况超出了编译器和链接器的能力。请记住,这些定义可以在任何编译单元中。构造函数和析构函数在这里调用 init() 或 cleanup() 没有任何非法行为,除非你知道它们将要做什么,并且 init() 和 cleanup() 调用纯虚函数也没有任何非法行为,除非你知道它们被调用的地方。
编译器或链接器完全不可能做到这一点。
因此标准必须允许编译和链接并将其标记为“未定义行为”。
当然,如果确实存在实现,编译器可以自由使用它。未定义的行为并不意味着它必须崩溃。只是标准并没有说它必须使用它。
请注意,在这种情况下,析构函数正在调用一个调用纯虚函数的成员函数,但你怎么知道它会这样做呢?它可能在调用纯虚函数的完全不同的库中调用某些东西(假设存在访问权限)。
Base::~Base()
{
someCollection.removeMe( this );
}
void CollectionType::removeMe( Base* base )
{
base->cleanup(); // ouch
}
如果 CollectionType 存在于完全不同的库中,则此处不会出现任何链接错误。简单的问题是这些调用的组合是不好的(但没有一个单独是错误的)。如果 removeMe 将调用纯虚拟 cleanup() 它不能从 Base 的析构函数中调用,反之亦然。
Base::init()
您必须记住的最后一件事Base::cleanup()
是,即使它们有实现,也永远不会通过虚函数机制(v-table)调用它们。它们只会被显式调用(使用完整的类名限定),这意味着实际上它们并不是真正的虚拟。允许您为他们提供实现可能会产生误导,这可能不是一个好主意,如果您想要这样一个可以通过派生类调用的函数,也许最好是受保护和非虚拟的。
本质上:如果您希望函数具有非纯虚函数的行为,例如您给它一个实现并在构造函数和析构函数阶段调用它,那么不要将其定义为纯虚函数。为什么将其定义为您不希望它成为的东西?
如果您只想阻止创建实例,您可以通过其他方式做到这一点,例如: - 使析构函数纯虚拟。- 使构造函数全部受到保护