c++polymorphismundefined-behaviorplacement-newvptr

placement new with derived class


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.

Actual output

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).

Part 1

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.

Part 2

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?

Part 3

The last block of code that uses pointers works "as expected" and is just here for sanity check


Solution

  • 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.