【问题标题】:Weird behaviour when extracting a known interface from a polymorphic container从多态容器中提取已知接口时的奇怪行为
【发布时间】:2015-11-11 15:39:08
【问题描述】:

谁能帮我理解这种行为? 简而言之:

  • 我已将多态对象存储在一个通用容器中。
  • 其中一些实现了特定的接口。我可以分辨出哪些。
  • 但是我不能使用这个界面。

以下是我总结的:

#include <iostream>
#include <vector>


// A base class
struct Base {
    // A polymorphic method
    virtual void describe() const {
        std::cout << "Base" << std::endl;
    };
    virtual ~Base(){
        std::cout << " Base destroyed" << std::endl;
    };
};

// A specific interface
struct Interface {
    virtual ~Interface(){
        std::cout << " Interface Destroyed" << std::endl;
    };
    virtual void specific() = 0;
};

// A derived class..
struct Derived : public Base, public Interface {
    virtual void describe() const {
        std::cout << "Derived" << std::endl;
    };
    virtual void specific() {
        std::cout << "Derived uses Interface" << std::endl;
    };
    virtual ~Derived() {
        std::cout << " Derived destroyed" << std::endl;
    };
};

int main() {

    // Test polymorphism:
    Base* b( new Base() );
    Derived* d( new Derived() );
    b->describe(); // "Base"
    d->describe(); // "Derived"
    // Ok.

    // Test interface:
    d->specific(); // "Derived uses Interface"
    Interface* i(d);
    i->specific(); // "Derived uses Interface"
    // Ok.

    // Here is the situation: I have a container filled with polymorphic `Base`s
    std::vector<Base*> v {b, d};
    // I know that this one implements the `Interface`
    Interface* j((Interface*) v[1]);
    j->specific(); // " Derived destroyed"
                   // " Interface destroyed"
                   // " Base destroyed"
    // Why?! What did that object do to deserve this?

    return EXIT_SUCCESS; // almost -_-
}

谁能告诉我我在那里缺少什么?

有趣的事实:如果我交换 Base::~BaseBase::describe 的定义,那么对象描述自己而不是被销毁。为什么顺序在方法声明中很重要?

【问题讨论】:

    标签: c++ interface polymorphism multiple-inheritance


    【解决方案1】:

    这是避免 C 风格强制转换的一个很好的理由。当你这样做时:

    Interface* j((Interface*) v[1]);
    

    那是reinterpret_cast。 C 风格的演员会尝试按以下顺序执行:const_caststatic_caststatic_cast 然后const_castreinterpret_castreinterpret_cast 然后const_cast。在这种情况下,所有这些演员表都是错误的! reinterpret_cast 尤其是未定义的行为,老实说,你为什么看到你看到的特定行为并不重要。未定义的行为是未定义的。

    你想要做的是:

    Interface* j = dynamic_cast<Interface*>(v[1]);
    

    这是通过运行时动态层次结构的正确转换,并且会给你正确 Interface*对应于v[1](或nullptr,如果v[1]没有这个运行时类型)。一旦我们解决了这个问题,j-&gt;specific() 就会打印出Derived uses Interface,正如你所期望的那样。


    问题可能与 vtable 对齐有关。当您进行重新解释转换时,由于Base 没有specific,因此该特定函数的偏移量可能与~Base() 对齐,因此效果是您直接调用析构函数 - 这是为什么你看到你所看到的。

    【讨论】:

    • 出于兴趣,Interface* j = static_cast&lt;Derived*&gt;(v[1]); 似乎也有效,安全吗?
    • @ChrisDrew 是的,这很好,因为您知道它是Derived*,所以static_cast 是可以的。然后从派生指针 (Derived*) 到指向基址的指针 (Interface*) 是一种标准转换,总是可以的。 dynamic_cast 更安全(但速度较慢),因为如果 v[1] 不是 Derived*,您将获得定义明确的结果(空指针)而不是看起来有效的指针,如果使用它会产生未定义的行为.
    • @lago-lito 是的,据我了解,dynamic_cast 确实有很大的开销,有人告诉我,至少有一种常见的实现部分基于类名的字符串比较。跨度>
    • @ChrisDrew 啊!很高兴知道。无论如何,我还没有试图从该程序中挤出每一次性能。这看起来是这里最好的做法:)
    • @Yakk 是 static_cast&lt;Derived*&gt; 起作用(并将 that 分配给 Interface*),static_cast&lt;Interface*&gt; 将无法编译。
    猜你喜欢
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多