2010-02-10 18 views
14

Sembra che il mio problema sia un errore in MSVC. Sto utilizzando Visual Studio 2008 con Service Pack 1 e il mio codice funziona con GCC (come testato su codepad.org).Errore di ereditarietà virtuale in MSVC

Qualche informazione ufficiale su questo bug? Qualche idea su come aggirarla? Il bug è stato risolto in VS2010? Tutti gli approfondimenti sarebbero molto apprezzati.

Il codice:

struct Base { 
    Base(int i = 0) : i(i) {} 
    virtual ~Base() {} 
    virtual Base *clone() const = 0; 

protected: 
    int i; 
}; 

struct A : virtual public Base { 
    A() {} 
    virtual A *clone() const = 0; 
}; 

struct B : public A { 
    B() {} 
    B *clone() const { return new B(*this); } 

    /// MSVC debugger shows that 'b' is for some reason missing the Base 
    /// portion of it's object ("Error: expression cannot be evaluated") 
    /// and trying to access 'b.i' causes an unhandled exception. 
    /// 
    /// Note: This only seems to occur with MSVC 
    B(const B &b) : Base(b.i), A() {} 
}; 

void foo(const A &elem) { 
    A *a = elem.clone(); 
    if (a) delete a; 
} 

int main() { 
    A *a = new B; 
    foo(*a); 
    delete a; 
} 
+1

Questo sembra essere un bug. – GManNickG

+2

Lo fa ancora in Visual Studio 2010. – Corey

+0

Ho notato che il codepad utilizza g ++ 4.1.2, quindi ho provato con Borland C++ 5.82 e funziona perfettamente. – Corey

risposta

8

Sembra come se il compilatore non è regolare correttamente il puntatore this quando si chiama attraverso A::clone. Se si rimuove la dichiarazione di A::clone allora tutto funziona correttamente.

Scavando in profondità, quando si ha A::clone, vtable assomiglia a questo:

[0x0] 0x002f1136 [thunk]:B::`vector deleting destructor'`vtordisp{4294967292,0}' (unsigned int) void * 
    [0x1] 0x002f11e0 [thunk]:B::clone`vtordisp{4294967292,0}' (void) void * 
    [0x2] 0x002f12ad [thunk]:B::clone`vtordisp{4294967292,4}' (void) void * 
    [0x3] 0x002f12a3 B::clone(void) void * 

E foo chiamate elem.__vfptr[2], compensando this in modo non corretto da -4 byte. Senza A::clone, vtable si presenta così:

[0x0] 0x00ee1136 [thunk]:B::`vector deleting destructor'`vtordisp{4294967292,0}' (unsigned int) void * 
    [0x1] 0x00ee11e0 [thunk]:B::clone`vtordisp{4294967292,0}' (void) void * 
    [0x2] 0x00ee12a3 B::clone(void) void * 

E foo chiama elem.__vfptr[1]. Ciò non modifica affatto this (e il codice presuppone che this sarà uguale a Base anziché B).

Quindi sembra che il compilatore presuppone che A::clone è un nuovo metodo virtuale e non sovrascrive Base::clone per determinare se A richiede una nuova tabella virtuale, ma poi qualche altro codice in seguito determina che A non ha bisogno di una tabella virtuale. È possibile verificare questo confrontando sizeof(B) con o senza una nuova funzione virtuale:

struct A : virtual public Base { 
    A() {} 
    virtual A *clone() const = 0; 
}; //sizeof(B)==16 

struct A : virtual public Base { 
    A() {} 
    virtual A *clone() const = 0; 
virtual const A *clone2() const { return this; } 
}; //sizeof(B)==20 

quindi è un bug del compilatore.

1

Sembrerebbe (da alcuni test) che il bug sia causato dalla combinazione di una classe base virtuale con un metodo virtuale puro che usa tipi di ritorno covarianti.

Dal momento che rilasciare il metodo virtuale puro dalla classe Base, o rendere Base una classe base non virtuale o rendere il metodo clone() non-covariante sembra risolvere il bug.

Immagino che questo sia risolto per me (dopo aver inviato un bug report alla MS), e mi sono anche lasciati con alcune opzioni per eluderlo. :)

Problemi correlati