Un caso molto comune: si cerca di chiamare un metodo virtuale puro dal costruttore ...
Costruttori
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Ora, supponiamo che il seguito dell'attuazione Interface()
Interface::Interface() {}
Quindi tutto va bene:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
È così doloroso chiamare puro dopo il costruttore, sarebbe meglio ridimensionare il codice giusto?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
Quindi possiamo farlo in una riga !!
Concrete myConcrete; // CRASHES VIOLENTLY
Perché?
Perché l'oggetto è costruito dal basso verso l'alto. Diamo un'occhiata.
istruzioni per costruire una classe Concrete
(circa)
allocare memoria sufficiente (ovviamente), e la memoria sufficiente per la (1 puntatore a funzione _vtable troppo per funzione virtuale, di solito nell'ordine in cui sono dichiarati , partendo dalla base più a sinistra)
chiamata Concrete
costruttore (il codice non si vede)
a> chiamata Interface
costruttore, che inizializzare il _vtabl e con i suoi puntatori
b> Chiama corpo Interface
costruttori (si scrive così)
c> sovrascrivono puntatori nella _vtable per quei metodi di override Calcestruzzo
d> Chiamata corpo Concrete
del costruttore (hai scritto che)
Quindi qual è il problema? Beh, guardate b>
e c>
ordine;)
Quando si chiama un metodo di virtual
dall'interno di un costruttore, non fa quello che stai sperando. Va a _vtable per cercare il puntatore, ma lo _vtable
non è ancora completamente inizializzato. Così, per tutto ciò che conta, l'effetto di:
D() { this->call(); }
è infatti:
D() { this->D::call(); }
Quando si chiama un metodo virtuale all'interno di un costruttore, non è necessario il tipo dinamico completo dell'oggetto in fase di costruzione, si ha il tipo statico dell'attuale Costruttore richiamato.
Nel mio Interface
/Concrete
esempio, significa Interface
tipo, e il metodo è virtuale pura, in modo che il _vtable non detiene un vero e proprio puntatore (0x0 o 0x01 per esempio, se il compilatore è abbastanza amichevole per i valori di debug di impostazione ai aiutarti lì).
distruttori
Incidentalmente, esaminiamo il caso Destructor;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
Allora, cosa succede a distruzione? Bene, la _vtable funziona bene e viene invocato il tipo di runtime reale ... cosa significa qui, tuttavia, è un comportamento indefinito, perché chissà cosa è successo a m_data
dopo che è stato cancellato e prima che fosse invocato il distruttore Interface
?Io non;)
Conclusione
Mai e poi mai chiamare metodi virtuali dall'interno costruttori o distruttori.
Se non è che, si è lasciato con una corruzione della memoria, dura fortuna;)
Può chiarire se il puntatore a funzione nel vtable è 0 o se è il questo puntatore che è nullo? O è un puntatore nell'oggetto che punta al vtable corrente? –
Il puntatore vtable è 1. L'arresto anomalo si verifica durante il tentativo di eseguire 'call * 0x1c (% eax)' (in at & t syntax) e il valore di eax è 1 (non zero come ho dichiarato in modo errato). – Tobias
Eseguilo in gdb, imposta un punto di riferimento all'indirizzo, guarda cosa lo sta scrivendo. –