Странное поведение при извлечении известного интерфейса из полиморфного контейнера

Может ли кто-нибудь помочь мне понять это поведение? Короче:

  • Я храню полиморфные объекты в общем контейнере.
  • Некоторые из них реализуют определенный интерфейс. Я могу сказать, какие из них.
  • Но я не могу использовать этот интерфейс.

Вот что я могу свести к следующему:

#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::~Base и Base::describe, тогда объект описывает себя, а не уничтожается. Как этот порядок имеет значение в объявлениях методов?

Ответ 1

Это хорошая причина, чтобы избежать приведения в стиле C. Когда вы выполните:

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

Это reinterpret_cast. Сценарий в стиле C попытается сделать, чтобы: const_cast, static_cast, static_cast, затем const_cast, reinterpret_cast, reinterpret_cast, затем const_cast. Все эти броски, в данном случае, ошибочны! reinterpret_cast, в частности, будет просто быть undefined, и, честно говоря, даже не имеет значения, почему вы видите конкретное поведение, которое вы видите & dagger;. undefined поведение undefined.

Вместо этого вы хотите:

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

Это правильное выражение в динамической иерархии времени выполнения и даст вам правильный Interface*, соответствующий v[1] (или nullptr, если v[1] не имел этого типа времени выполнения). Как только мы исправим это, тогда j->specific() печатает Derived uses Interface, как и следовало ожидать.


& dagger; Вероятно, проблема связана с выравниванием vtable. Когда вы выполняете реинтерпрет, так как Base не имеет specific, возможно, что смещение этой конкретной функции выравнивается с помощью ~Base(), поэтому эффект заключается в том, что вы непосредственно вызываете деструктор, который почему вы видите то, что видите.