2015-08-18 38 views
12

Qualcuno potrebbe aiutarmi a capire questo comportamento? Per essere corti:Comportamento strano durante l'estrazione di un'interfaccia nota da un contenitore polimorfico

  • Ho memorizzato oggetti polimorfici in un contenitore comune.
  • Alcuni di essi implementano un'interfaccia specifica. Posso dire quali.
  • Ma non posso usare questa interfaccia.

Ecco quello che ho boilt giù a:

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

Qualcuno può dirmi che cosa manco qui?

Fatto interessante: Se scambiare le definizioni di Base::~Base e Base::describe, quindi l'oggetto stesso descrive invece di essere distrutto. Come mai l'ordine conta nelle dichiarazioni dei metodi?

risposta

21

Questa è una buona ragione per evitare i cast di tipo C. Quando si esegue:

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

Questo è un reinterpret_cast. Un cast in stile C proverà a fare, nell'ordine: const_cast, static_cast, static_cast quindi const_cast, reinterpret_cast, reinterpret_cast quindi const_cast. Tutti questi calchi, in questo caso, sono sbagliati! In particolare, il reinterpret_cast sarà solo un comportamento indefinito e, onestamente, non importa nemmeno il motivo per cui vedi il comportamento specifico che vedi & dagger;. Il comportamento indefinito non è definito.

Che cosa si vuole fare, invece è:

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

Questo è il cast corretto attraverso la gerarchia dinamica fase di esecuzione, e vi darà la correttaInterface* corrispondente al v[1] (o nullptr, se v[1] no avere questo tipo di runtime). Una volta risolto, lo j->specific() stampa Derived uses Interface, come previsto.


e pugnale; Probabilmente, il problema ha a che fare con l'allineamento del vtable. Quando esegui il reinterpretazione del cast, dal momento che Base non ha un specific, è possibile che l'offset di quella particolare funzione sia allineato con ~Base(), quindi l'effetto è che stai chiamando direttamente il distruttore - che è il motivo per cui vedi quello che vedi .

+0

Fuori interesse, 'Interface * j = statico_cast (v [1]);' sembra funzionare, è sicuro? –

+0

@ChrisDrew Finché tu * sai * che '* v [1]' è in realtà un'istanza di 'Derived' (o una classe derivata da quello), è sicuro. – Angew

+1

@ChrisDrew Sì, va bene perché si sa che è un derivato *, quindi il 'static_cast' è OK. E poi il puntatore al derivato ('Derived *') al puntatore-base ('Interface *') è una conversione standard, quella è sempre OK. 'dynamic_cast' è più sicuro (ma più lento) perché se' v [1] 'non fosse un' Derived * ', otterresti un risultato ben definito (un puntatore nullo) invece di un puntatore dall'aspetto valido che produce un comportamento indefinito se lo usi – Barry