【问题标题】:What happens when a destructor calls an abstract function当析构函数调用抽象函数时会发生什么
【发布时间】:2018-07-23 21:52:08
【问题描述】:

我无法理解以下代码中崩溃的原因:

class A {
public:
    virtual ~A() { goo(); }
    void goo() { absFoo(); }
    virtual void absFoo() = 0;

};

class B : public A {
public:
    void absFoo() { cout << "In B \n"; }
};

int main() 
{
    B b1;
    b1.goo();
}

main按预期打印“In B”,但最后当它崩溃时,我也无法调试它,编译器弹出一条奇怪的消息。

所以我的问题是,当 A 的析构函数调用“goo()”时,“absFoo()”会崩溃吗 因为我们指的是抽象函数?

或者编译器真的会在派生类中寻找定义吗?(而且它已经不存在了,因为它事先被破坏了所以它崩溃了)

我知道如果我们直接从析构函数中调用“absFoo()”,原因应该是抽象函数。但由于这里是一个调用“absFoo()”的外部函数,我无法理解真正的原因。

【问题讨论】:

  • aside:main 必须返回 int,而不是 void
  • 你实际上不能直接从A的析构函数调用absFoo。在这种情况下,程序将无法编译,句号。
  • 原因是非法的。并且语言规范不会保护您免于违反此规则。
  • 这看起来类似于另一个问题stackoverflow.com/questions/12092933/…
  • @Martin - 您已经拥有问题的答案。 :-) 在单独的函数中隐藏 absFoo 调用没有区别 - 当您输入 A 时,B 的析构函数已经消失,this 的动态类型为 A*

标签: c++ constructor crash virtual abstract


【解决方案1】:

当析构函数调用抽象函数时会发生什么

首先,让我们考虑当析构函数调用任何虚函数时会发生什么(顺便说一句,构造函数也是如此):当在T的析构函数中调用虚函数foo时,调用不会动态分派给派生类型的实现(任何派生对象的生命周期都已经结束),但静态分派给实现T::foo

如果T::foo 是纯虚拟的,那么在没有动态调度的情况下调用它会产生未定义的行为。这就是在析构函数(或构造函数)中(间接)调用纯虚函数时发生的情况。

【讨论】:

  • 现在我明白了,我的错误是认为在另一个函数中“隐藏”纯虚函数会产生任何影响,但这并不重要,因为它是在这个阶段静态实现的(破坏)。谢谢!
  • 注意:虽然理论上调用纯虚函数是UB,但实际上在Itanium ABI中我认为它可以保证生成异常(这要好得多)。
【解决方案2】:

只是为了补充已经接受的答案,这是来自cppreference 的文档。

当一个虚函数被直接或间接调用时 构造函数或析构函数(包括在构造过程中或 销毁类的非静态数据成员,例如在一个成员 初始化器列表),调用适用的对象是 正在构造或销毁的对象,调用的函数是 构造函数或析构函数类中的最终覆盖器,而不是一个 在派生更多的类中覆盖它。

换句话说,在构造或销毁期间,派生更多的类不存在。

【讨论】:

    【解决方案3】:

    随着对象被解构,vtable 被更新以匹配对象的新状态。

    由于您删除了最后一个函数,编译器将执行它认为合适的任何操作;在 Visual Studio 中进行调试编译的情况下,将回退到中止报告调用了纯虚函数。

    然而,vtable 不是标准的一部分,它是一个实现细节,并且不需要您的程序崩溃;当你调用一个纯虚函数时,这被认为是最好的事情。

    【讨论】:

      猜你喜欢
      • 2012-05-10
      • 1970-01-01
      • 2020-10-29
      • 1970-01-01
      • 2020-09-13
      • 2014-11-11
      • 2013-09-17
      • 1970-01-01
      相关资源
      最近更新 更多