It seems that my problem is a bug in MSVC. I'm using the Visual Studio 2008 with Service Pack 1, and my code works with GCC (as tested on codepad.org).
Any official info on this bug? Any ideas how to work around it? Is the bug fixed in VS2010? All insights would be greatly appreciated.
The code:
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;
}
GCC is a fine compiler, and can produce code that has pretty much the same performance, if not better, than MSVC. It is missing some low-level Windows-specific features though.
MSVC is doing the compilation job significantly faster than MinGW-w64. The DLL sizes are comparable, if optimization is set to "-O2" for MinGW-w64, with "-O3" the DLLs from MinGW-w64 are larger. Binary files compiled with MinGW-w64 are performing significantly better than those compiled with MSVC.
First introduced in Visual Studio 2015, the MSVC compiler has included C++ language mode switches to indicate the targeted level of standard conformance and we now support three stable language modes: /std:c++14 , /std:c++17 , /std:c++20 (as of VS 2019 v16. 11) and one preview mode ( /std:c++latest ).
Virtual inheritance is a C++ technique that ensures only one copy of a base class's member variables are inherited by grandchild derived classes.
It looks as though the compiler is not correctly adjusting the this
pointer when calling through A::clone
. If you remove the declaration of A::clone
then everything works fine.
Digging in deeper, when you have A::clone
, the vtable looks like this:
[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 *
And foo calls elem.__vfptr[2]
, offsetting this
incorrectly by -4 bytes. Without A::clone
, the vtable looks like this:
[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 *
And foo calls elem.__vfptr[1]
. That does not adjust this
at all (and the code assumes that this
will be equal to Base
instead of B
).
So it looks like the compiler assumes that A::clone
is a new virtual method and doesn't override Base::clone
when determining whether A
requires a new virtual table, but then some other code later determines that A
does not need a virtual table. You can verify this by comparing sizeof(B)
with or without a new virtual function:
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
So it's a compiler bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With