0

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

curiousguy
  • 8,038
  • 2
  • 40
  • 58
YePhIcK
  • 5,816
  • 2
  • 27
  • 52
  • 4
    Virtual calls only work through pointers and references. Not on direct objects. – Galik Oct 17 '18 at 03:11
  • 2
    Also I think you have some undefined behavior in a couple of places there. – Galik Oct 17 '18 at 03:15
  • I think this is related: https://stackoverflow.com/questions/15188894/why-doesnt-polymorphism-work-without-pointers-references – Galik Oct 17 '18 at 03:17
  • `b = D{};`, also, in fact, is wrong, since derived classes can use the base class's constructor but not the opposite... (Here, b is the base class and can **only** use its own constructor... – Ruks Oct 17 '18 at 03:21
  • @Ruks "_derived classes can use the base class's constructor but not the opposite_" What do you mean? – curiousguy Oct 28 '18 at 21:10

1 Answers1

0

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.

Galik
  • 47,303
  • 4
  • 80
  • 117
  • "_First you did not call the destructor for the base object B._" There is no reason to: that particular destructor has no functional effect. – curiousguy Oct 22 '18 at 17:22
  • @curiousguy The destructor may not have a *functional effect* with respect to the program but there is no telling how it affects the internal state of the compiler and its decision making process. It is still *undefined behavior* if the destructor is not called. – Galik Oct 22 '18 at 17:51
  • "_It is still undefined behavior_" since when? – curiousguy Oct 22 '18 at 18:53