Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::unique_ptr<T[]> with an array of derived objects, use of deleted function

In my code for numerical physics, I need to create an array of Derived objects using the unique_ptr with their type being the Base class. Normally, I would have:

// Header file of the Base class
class Particle{
public:
    Particle();             // some constructor
    virtual ~Particle();    // virtual destructor because of polymorphism
    virtual function();     // some random function for demonstration
};

// Header file of the Derived class
class Electron : public Particle{
public:
    Electron();
    // additional things, dynamic_cast<>s, whatever
};

Later in my code, to create an array of Derived objects with the Base type pointer, I would do

Particle* electrons = new Electron[count];

The advantage is that I am able to use the array in a really convenient way of electrons[number].function(), because the incremental value in [] is actually the address of the memory that points to the proper instance of the object Electron in the array. However, using raw pointers gets messy, so I decided to use the smart pointers.

Problem is with the definition of the Derived objects. I can do the following:

std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);

which creates the array of polymorphic Electrons and uses even the proper call of delete[]. The problem lies in the way of calling the specific objects of the array, as I have to do this:

electrons.get()[number].function();

and I don't like the get() part, not a little bit.

I could do the following:

std::unique_ptr<Particle[]> particles(new Particle[count]);

and yes, call the instances of Particle type in the array with the

particles[number].function();

and everything would be fine and dandy, except for the part that I am not using the specific details of the class Electron, therefore the code is useless.

And now for the funny part, let's do one more thing, shall we?

std::unique_ptr<Particle[]> electrons(new Electron[count]);

BOOM!

use of deleted function ‘std::unique_ptr<_Tp [], _Dp>::unique_ptr(_Up*) [with _Up = Electron; <template-
 parameter-2-2> = void; _Tp = Particle; _Dp = std::default_delete<Particle []>]’

What is going on?

like image 392
bluecore Avatar asked Nov 01 '15 11:11

bluecore


2 Answers

std::unique_ptr is preventing from shooting yourself in the foot, as std::default_delete<T[]> calls delete[], which has the behaviour specified in the standard

If a delete-expression begins with a unary :: operator, the deallocation function’s name is looked up in global scope. Otherwise, if the delete-expression is used to deallocate a class object whose static type has a virtual destructor, the deallocation function is the one selected at the point of definition of the dynamic type’s virtual destructor (12.4). 117 Otherwise, if the delete-expression is used to deallocate an object of class T or array thereof, the static and dynamic types of the object shall be identical and the deallocation function’s name is looked up in the scope of T.

In other words, code like this:

Base* p = new Derived[50];
delete[] p;

is undefined behaviour.

It may have seem to work on some implementations - there, the delete[] call looks up the size of the allocated array and calls destructors on the elements - which requires the elements to have a well known size. Since the size of derived objects may differ, the pointer arithmetic goes wrong, and the destructors are called with the wrong address.

Let's review what you tried:

std::unique_ptr<Particle[]> electrons(new Electron[count]);

there's a code in std::unique_ptr's constructor that detects these violations, see cppreference.

std::unique_ptr<Particle, std::default_delete<Particle[]>> electrons(new Electron[count]);

is undefined behaviour, you essentially tell the compiler that delete[] is a valid way to release the resources you push to the constructor of electrons, which isn't true, as mentioned above.

...but wait, there is more (priceless comment by @T.C.):

For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar ([conv.qual]), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]

This means not only deleting an array is undefined behaviour, but so is indexing!

Base* p = new Derived[50]();
p[10].a_function(); // undefined behaviour

What does it mean to you? This means you shouldn't use arrays polymorphically.

The only safe way with polymorphism is to use std::unique_ptr pointing to derived objects, like std::vector<std::unique_ptr<Particle>> (we don't have polymorphic use of array there, but arrays with polymorphic objects there)

Since you mention that performance is critical, then dynamically allocating every Particle will be slow - in this case you can:

  • use an object pool
  • make use of flyweight pattern
  • refactor it to avoid inheritance
  • use std::vector<Electron> or std::unique_ptr<Electron[]> directly.
like image 115
milleniumbug Avatar answered Sep 19 '22 14:09

milleniumbug


The problem with your design is that objects are derived and polymorphic, but not the arrays of objects.

For example, Electron could have additional data that a Particle doesn't have. Then the size of an Electron object would no longer be the same size as a Particle object. So the pointer arithmetic that is needed to access array elements would not work anymore.

This problem exist for raw pointers to array as well as for unique_ptrto array. Only the objects themselves are polymorphic. If you want to use them without the risk of slicing, you'd need an array of pointers to polymorphic objects.

If you look for additional arguments explaining why this design should be avoided, you may have a look at the section of Scott Meyers' book "More effective C++" titled "Item 3: never treat arrays polymorphically".

Alternative: change your design

For example, use a vector of the real type to create your objects. And use a vector to a polymorphic Particle pointer to use these objects polymorphically:

vector<Electron>myelectrons(count);   // my real object store 
vector<Particle*>ve(count, nullptr);  // my adaptor for polymorphic access
transform(myelectrons.begin(), myelectrons.end(), ve.begin(), 
                [](Particle&e){return &e;} );  // use algorithm to populate easlily 
for (auto x: ve)  // make plain use of C++11 to forget about container type and size
   x->function(); 

Here a live demo:

like image 42
Christophe Avatar answered Sep 22 '22 14:09

Christophe