13

I want to have a vector of pointers to objects in my class. To avoid making a destructor for it I wanted to use std::unique_ptr, as the objects are created/owned/destructed in my class, but I have a compiler error I can't understand. Next code will serve as a short sample for my problem:

std::unique_ptr<int> createPtr(int value)
{
    std::unique_ptr<int> ptr(new int(value));
    return ptr;
};

int main()
{
    std::vector<std::unique_ptr<int>> vec;
    vec.push_back(createPtr(1));

    std::unique_ptr<int> ptr = createPtr(2);
    vec.push_back(ptr);//error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'
}

Can you please explain me why I get this error and what is the correct usage for std::unique_ptr?

iammilind
  • 62,239
  • 27
  • 150
  • 297
Mircea Ispas
  • 18,498
  • 26
  • 109
  • 202

3 Answers3

15

Either:

vec.push_back(std::move(ptr));

Or:

vec.emplace_back(createPtr(2));
Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • 2
    It solves the problem, but can you please explain the problem? – Mircea Ispas Jan 23 '13 at 16:15
  • 7
    @Felics The problem is that `unique_ptr` does not allow copy construction, so trying to `push_back` the `ptr` fails because it attempts to invoke the copy constructor. The move constructor, however, is accessible, and both of Kerrek's solutions call that instead of the copy constructor. – Praetorian Jan 23 '13 at 16:18
  • @Praetorian What about `emplace_back`? – 0x499602D2 Jan 23 '13 at 17:08
  • @David Same deal, `createPtr` returns a `unique_ptr` by value and `emplace_back` will attempt to call a `unique_ptr` constructor with this argument. The matching constructor is the move constructor. – Praetorian Jan 23 '13 at 17:13
  • @David emplace_back means, create inplace.No copy or move is done for the following. `vec.emplace_back(new int(34))` The value is inplace constructed in the vector – balki Jan 23 '13 at 17:39
  • @David: `emplace`-like functions allow for *explicit* conversions and constructions. – Kerrek SB Jan 23 '13 at 20:25
13

Consider the vec.push_back(). It has two overloads which take either const std::unique_ptr<int>& or std::unique_ptr<int>&&. The first overload can never be used. This is because the vector<> requires the type to be assignable, or movable (C++11 addition). 'Assignability' implies copying. push_back(const T&) will try to copy (assign) the incoming value to the new space at the end of the container. std::unique_ptr<> represents a resource that is owned by a single owner. By copying it (the pointer) multiple owners would be present. Because of that unique_ptr is not copyable.

Having said all of it, you can only use the T&& overload.

createPtr() returns std::unique_ptr<int>, but as this is a temporary result (function return value) it is considered a rvalue reference (implicitly). That is why this can be used.

ptr is just a std::unique_ptr<int> which is a lvalue reference (no matter if you put && next to it, as named rvalues are still treated as lvalues). Lvalue is never implicitly converted to rvalue (completely unsafe). But you can basically tell the compiler "Ok, you can take the object I pass you, and I promise I won't expect the argument to be left intact" by using std::move().

Red XIII
  • 5,057
  • 4
  • 22
  • 29
3

You have a unique_ptr:

std::unique_ptr<int> ptr = createPtr(2);

Now you put a copy of it in the vector:

vec.push_back(ptr);

Now you have two unique_ptrs with the same value. If that was allowed it would not be unique.

Jonathan Wakely
  • 153,269
  • 21
  • 303
  • 482