Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Placement new and inheritance

Good evening everyone.
A code snippet will be worth a thousand words :

// Storage suitable for any of the listed instances
alignas(MaxAlign<Base, Derived1, Derived2>::value)
char storage[MaxSize<Base, Derived1, Derived2>::value];

// Instanciate one of the derived classes using placement new
new (storage) Derived2(3.14);


// Later...

// Recover a pointer to the base class
Base &ref = *reinterpret_cast<Base*> (storage);

// Use its (virtual) functions
ref.print();

// Destroy it when we're done.
ref.~Base();

As you can see, I want to access the instance only through its base class, and without actually storing the base class pointer. Note that in the second part, the Derived2 type information will be lost, so I'm left with only storage and the guarantee that one derived instance is in it.

As placement new never adjusts the destination pointer, this boils down to using reinterpret_cast to upcast to a base class. Now I know that this is dangerous, since the more appropriate static_cast adjusts the pointer in some cases. [1]

And indeed, it triggers Undefined Behaviour. You'll find the full code here on Coliru (g++ 4.9.0), where it promptly crashes at runtime. Meanwhile on my PC (g++ 4.8.2), everything is fine. Note that on g++ 4.9, outputting both pointers before calling the functions shows identical values... and works.

So I tried to take the problem backwards : nudging the derived instance so that a pointer to Base will be equal to storage.

void *ptr = static_cast<Derived2*>(reinterpret_cast<Base*>(storage));
new (ptr) Derived2(3.14);

No luck. The thing still runs fine on g++ 4.8, and falls in flames on g++ 4.9.

Edit : come to think of it, the above isn't that smart. Because, what if the derived instance ends up before storage... Ouch.

So my question is : is there a solution to what I'm trying to achieve ? Or, are the cases mentioned at [1] sufficiently well-defined that I can write code that will work with a subset of classes (like, no virtual inheritance) ?

like image 740
Quentin Avatar asked Jul 24 '14 18:07

Quentin


People also ask

Does placement new call constructor?

A placement new expression first calls the placement operator new function, then calls the constructor of the object upon the raw storage returned from the allocator function.

What does the placement new do?

Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.

How can you overcome the diamond problem in inheritance?

The Diamond Problem is fixed using virtual inheritance, in which the virtual keyword is used when parent classes inherit from a shared grandparent class. By doing so, only one copy of the grandparent class is made, and the object construction of the grandparent class is done by the child class.

Is new an operator?

new keywordThe new operator is an operator which denotes a request for memory allocation on the Heap. If sufficient memory is available, new operator initializes the memory and returns the address of the newly allocated and initialized memory to the pointer variable.


1 Answers

I've just modified your code a bit, it seems that reinterpret_cast is not the problem. The minimum code that could replicate this error is as such

Coliru link: http://coliru.stacked-crooked.com/a/dd9a633511a3d08d

#include <iostream>

struct Base { 
    virtual void print() {}
};

int main(int, char**) {
   Base storage[1];
   storage[0].print();

   std::cout <<"Succeed";
   return 0;
}

The sufficient conditions to trigger this error are

  1. the "storage" variable is allocated on the stack as an array

  2. the print() must be a virtual method

  3. the compiler option should be -O2

If you use -O1, the program compiles and runs without a problem.

Besides, this error seems to only show up when compiled with g++ 4.9.0. It runs fine if compiled with VS2012/2013 or g++ 4.7.2 (you can test it on http://www.compileonline.com/compile_cpp_online.php)

Judging from the above, I think this may be a compiler specific problem.

Note: The actual output of the program given in this answer is different from the OP's. It does not show Segmentation Fault. However, when run successfully, it should print "Succeed", which is not shown when it is run on the Coliru.

Edit: Modified the code to replicate the error. No derived class needed.

like image 127
Hsi-Hung Shih Avatar answered Oct 14 '22 03:10

Hsi-Hung Shih