C++ gurus. Need your help with this little head scratcher:
#include <iostream>
struct B{
virtual ~B() = default;
virtual void talk() { std::cout << "Be-e-e\n"; }
};
struct D:B{
void talk() override { std::cout << "Duh\n"; }
~D() { std::cout << "~D()\n"; }
};
int main(){
B b{}; // vptr points to B
new (&b) D; // vptr now points to D
b.talk(); // "Be-e-e" (why? shouldn't the vptr be used?)
b = D{}; // "~D()" (why? shouldn't the copying be elided?)
b.talk(); // "Be-e-e"
B*b1{new D};
b1->talk(); // "Duh"
delete b1; // "~D()"
return 0;
}
The code is pretty straightforward: have a base object on stack, placement-new a derived one into it (yes, eeew, but bear with me) and calling a virtual method, expecting a derived's output to be printed.
The code above produces the following output:
Be-e-e ~D() Be-e-e Duh ~D()
That behavior is universally observed on MSVC, gcc, clang, and a few online compilers I tried it on (which is an extremely strong indication that it is I who am wrong).
The placement-new re-news a derived-type object into the base-type memory. And that updates the vptr to point to the derived-type's vtable (directly observed in the debugger).
Main question: is that the expected behavior? (I want to say "yes" so if it is not - please explain to me)
I want to believe that performing a placement-new (provided there's enough memory for the derived-type object) should in-place initialize a brand new object of the derived-type.
If my understanding is correct, the first b.talk()
should output "Duh"
as the object now is of derived-type. Why is it still printing "Be-e-e"
?
Assigning a derived-type object into the base-type object (in addition to causing object splicing) does not copy the vptr, so that second "Be-e-e"
output is expected, provided the object is still of the base-type when we get to that line of code.
Why is there a ~D()
call in the b = D{};
assignment? Isn't it a temporary that should be copy-elided with no need for a destructor call on that temporary?
The last block of code that uses pointers works "as expected" and is just here for sanity check
Looking at the code:
B b{}; // vptr points to B
new (&b) D; // vptr now points to D
This is a potential problem for two reasons. First you did not call the destructor for the base object B
. Second the size of B
could be too small to accommodate a D
type object.
b.talk(); // "Be-e-e" (why? shouldn't the vptr be used?)
Virtual calls only work when calling through a pointer or a reference. A direct function call like this never uses virtual dispatch.
b = D{}; // "~D()" (why? shouldn't the copying be elided?)
Because b
is declared as type B
and you can not elide a copy between different types like D
and B
.