Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird behaviour when extracting a known interface from a polymorphic container

Could anyone help me understand this behaviour? To be short:

  • I have stored polymorphic objects in a common container.
  • Some of them implement a specific interface. I can tell which ones.
  • But I can't use this interface.

Here is what I have boilt it down to:

#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 -_-
}

Can anyone tell me what I am missing there?

Interesting fact: If I swap the definitions of Base::~Base and Base::describe, then the object describes itself instead of being destroyed. How come the order matters in method declarations?

like image 316
iago-lito Avatar asked Aug 18 '15 15:08

iago-lito


1 Answers

This is a good reason to avoid C-style casts. When you do:

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

That is a reinterpret_cast. A C-style cast will try to do, in order: const_cast, static_cast, static_cast then const_cast, reinterpret_cast, reinterpret_cast then const_cast. All of these casts, in this case, are wrong! reinterpret_cast in particular will just be undefined behavior and it honestly doesn't even matter why you see the specific behavior you see. Undefined behavior is undefined.

What you want to do instead is:

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

That is the correct cast through the runtime dynamic hierarchy, and will give you the correct Interface* corresponding to v[1] (or nullptr, if v[1] did not have this runtime type). Once we fix that, then j->specific() prints Derived uses Interface, as you would expect.


Likely, the issue has to do with the vtable alignment. When you do the reinterpret cast, since Base does not have a specific, it's possible that the offset of that particular function lines up with ~Base(), so the effect is that you're directly calling the destructor - which is why you see what you see.
like image 65
Barry Avatar answered Sep 22 '22 16:09

Barry