【问题标题】:Dynamic cast in destructor析构函数中的动态强制转换
【发布时间】:2020-05-15 08:18:16
【问题描述】:

这段代码合法吗?

class Base1 {
};

class Base2 {
public:
    virtual ~Base2() {
        if (!dynamic_cast<Base1*>(this))
            std::cout << "aaaa" << std::endl;
    }
    Base2() {
    }
};

class MyClass: public Base1, public Base2 {
public:
    MyClass() {
    }
    virtual ~MyClass() {
        std::cout << "bbb" << std::endl;
    }
};

int main() {
    MyClass s;
    return 0;
}

我看到两张照片,但我应该只看到一张。我猜动态演员是错误的。可以做这样的检查吗?

【问题讨论】:

  • 您能澄清一下您要检查的内容吗? Base2 是否想知道它是否是具有 Base1 的派生类的基础?
  • 是的,我想检查 Base2 是否“this”也是 base1 的子节点
  • “但我应该只看到一个”为什么?为什么你对合法性有怀疑?
  • @greywolf82 哦,对不起,我错过了!
  • dynamic_cast 在构造函数和析构函数中的行为是否不同?

标签: c++ polymorphism destructor multiple-inheritance dynamic-cast


【解决方案1】:

也许我自己找到了解决方案,回复是不,不可能:

来自bullet 6 of cppreference.com documentation

在构造函数或析构函数中使用 dynamic_cast 时(直接 或间接),并且表达式是指当前的对象 在建造/销毁中,该物体被认为是 大多数派生对象。 如果 new-type 不是指针或引用 构造函数/析构函数自己的类或其基础之一,行为 未定义。

另见标准的 [class.cdtor]/6。

由于我在 Base2 析构函数中强制转换为 Base1,因此此行为未定义。

【讨论】:

  • [class.cdtor]/6,供参考。抱歉,不是 5。在 C++17(草案 N4659)中是 5,现在看起来是 /6
  • @ChrisMM 该段似乎没有提到此答案中引用的未定义行为。实际上我在标准中的任何地方都找不到这个限制。
  • 我认为 cppreference 错误地写成了“new-type”,而它应该是“expression”。链接的标准段落确实说,如果操作数引用正在破坏的对象,则操作数需要具有(静态)类型的析构函数或其基类。在问题的代码中 this 是析构函数的类型,所以我看不到 UB 应用。然而,同一标准段落确实说,被破坏的对象的最派生类型将被视为析构函数的类,因此可以观察到另一个答案中解释的行为。
  • @walnut,只是发布了标准中的相关段落,而不是我的答案。我同意它不是UB。
【解决方案2】:

dynamic_cast 在这种情况下是明确定义的。观察两行输出是正确的。

您错误地认为Base2 的析构函数中this 是派生类。此时派生类部分已经被销毁,不能再是派生类了。其实在Base2的析构函数运行的时候,this所指向的对象只是一个Base2对象。由于Base2Base1没有任何关系,所以dynamic_cast返回一个空指针,相应地输入条件。

编辑:standard says

dynamic_­cast 用于构造函数[...] 或析构函数[...] 时,如果dynamic_­cast 的操作数引用正在构造或销毁的对象,则该对象被认为是是具有构造函数或析构函数类的类型的最派生对象。如果dynamic_­cast 的操作数指的是正在构造或销毁的对象,并且操作数的静态类型不是指向构造函数或析构函数自己的类或其基类之一的指针或对象,则dynamic_­cast 将导致 undefined行为。

操作数this 指的是正在销毁的对象。因此,析构函数的类 (Base2) 被认为是派生最多的类,这就是对象与目标类型 (Base1*) 没有任何关系的原因。此外,操作数this 的静态类型是Base2* const,这显然是指向析构函数自己的类的指针。因此,关于未定义行为的规则不适用。总之,我们有明确定义的行为。

【讨论】:

  • 这与另一个答案相矛盾,后者声明(引用标准)代码具有未定义的行为。你需要一些好的论据来反对,只是说
  • @foreknownas_463035818 另一个答案是引用 cppreference,而不是标准。那可能还不清楚。它提到的标准段落似乎与 cppreference 不一样。
  • @walnut 我认为这是标准的引用,同时它被编辑为引用 cppref。无论如何,答案是矛盾的,一个有来源支持它,另一个没有,只是说......
  • @walnut 我检查了自己,另一个答案中提到的[class.cdtor]/6 声明与 cppref 相同:“如果 dynamic_cast 的操作数是指正在构造或销毁的对象以及操作数的静态类型不是构造函数或析构函数自己的类或其基类之一的指针或对象,dynamic_cast 会导致未定义的行为。"
  • 我已经添加了我对标准的解释。
【解决方案3】:

我同意@j6t 的回答,但这里是标准参考的扩展推理。

dynamic_cast 对正在构造和销毁的对象的特殊行为由 C++17 标准(最终草案)的 [class.cdtor]/5 描述,之前的标准版本也对其进行了等效描述。

特别是它说:

dynamic_­cast在析构函数中使用[...]时,[...],如果dynamic_­cast的操作数指的是正在构造或销毁的对象,则该对象被认为是最具有 [...] 析构函数类的类型的派生对象。如果dynamic_­cast 的操作数引用[...] 破坏下的对象,并且操作数的静态类型不是指向[...] 析构函数自己的类或其基类之一的指针或对象,则dynamic_cast 导致未定义的行为。

未定义的行为在这里不适用,因为操作数是表达式this,它通常具有指向析构函数自己的类的指针类型,因为它出现在析构函数本身中。

但是,第一句指出dynamic_cast 的行为就像*thisBase2 类型的最派生对象,因此转换为Base1 永远不会成功,因为Base2 不是派生的来自Base1dynamic_cast&lt;Base1*&gt;(this) 将始终返回一个空指针,从而导致您看到的行为。


cppreference.com 声明如果转换的目标类型不是析构函数类的类型或其基类之一,而不是将其应用于操作数类型,则会发生未定义的行为。我认为这只是一个错误。可能在第 6 点中提到的“new-type”应该是“expression”,这将使它符合我上面的解释。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-12
    • 2015-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多