从多态容器中提取已知接口时的奇怪行为

Weird behaviour when extracting a known interface from a polymorphic container

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

这是我归结为:

#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* 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->specific() 打印 Derived uses Interface,正如您所期望的那样。


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