“零规则”也适用于具有虚拟方法的类吗?

15
我发现零规则,正如Peter Sommerlad的幻灯片(第32页)中所提到的一样,非常有说服力。
尽管如此,我似乎记得有一个严格的规则,即如果类具有虚成员并且实际上是派生类,则必须定义析构函数为虚函数
struct Base {
    virtual void drawYourself();
    virtual ~Base() {}
};
struct Derived : public Base {
    virtual void drawYourself();
};

析构函数的主体甚至可以为空(只需要在虚函数表中有条目即可)。

我似乎记得在使用继承层次结构时

int main() {
    Base *obj = new Derived{};
    obj->drawYourself(); // virtual call to Derived::drawYourself()
    delete obj; // Derived::~Derived() _must_ be called
}

如果 delete obj 调用正确的析构函数是很重要的。如果我完全省略了析构函数定义,那么它将不会变成虚函数,因此错误的析构函数将被调用吗?
struct Base {
    virtual void drawYourself();
    // no virtual destructor!
};

这让我想到了我的最后一个问题:
  • “零规则”在具有虚拟方法的层次结构中是否也适用?
  • 还是在这些情况下需要定义虚拟析构函数?

编辑:正如我在回答中被提醒的那样,我对问题的第一版做出了错误的假设。相关(虚拟)析构函数在Base中,而不是Derived。但我的问题仍然存在:我需要声明(虚拟)析构函数吗?


1
  1. 当然。
  2. 当您需要进行多态的delete操作时,您需要定义一个虚析构函数。
- Shoe
6
= default特殊成员函数并不以任何方式违反Ro0,重要的是,您不需要自己实现它们的_功能_,而是从构建块(如unique_ptrvector)派生出来,这些构建块处理单个职责。老实说,即使没有= default,析构函数也可以正常工作-因为您没有在其中手动执行任何操作。一切仍由适当成员的析构函数处理。 - Xeo
你在基类中有一个错误。 - BЈовић
6
值得注意的是,如果使用Derived*(或make_shared<Derived>)构造std::shared_ptr<Base>,则其将通过调用Derived::~Derived()进行清理,并且不需要将Base::~Base()声明为虚函数。 - Ben Voigt
3
@towi说的对于std::shared_ptr是正确的。在std::unique_ptr中,你不会得到这种行为,因为自定义删除器是类型的一部分(即所有的std::unique_ptr<Base, ThisDeleterType>将以相同的方式删除对象)。因此,unique_ptr自定义删除器对于多态性并不实用。 - Ben Voigt
显示剩余5条评论
1个回答

8

实际上,必须声明基本析构函数为虚函数,并且在派生类中自动变成虚函数:

struct Base {
    virtual void drawYourself();
    virtual ~Base() = default;
};

struct Derived : public Base {
    virtual void drawYourself();
};

但除此之外,零规则仍然适用。

如果您按照以前的方式进行操作,或者如果您省略了virtual析构函数,当通过基础指针delete派生对象时,将会出现未定义的行为。


我太傻了。让我们修改我的问题。 - towi
3
你可能需要将“构造函数”更改为“析构函数”。 - Brian Bi
1
不是“除此之外”的 - 这仍然属于具有= default的Ro0。 - Xeo
1
@Xeo 是的,也许如此,但是仍然需要记住它。并且在它前面写上virtual而且只有对于存在另一个虚方法的类才这样做。并且如果没有派生类可以省略它(实际上没有意义)。无论如何,仍然需要解释为什么。 - towi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接