29
votes

What is the proper way of transferring ownership of a std::vector<unique_ptr<int> > to a class being constructed?

Below is a code representation of what I want to do. I realize it is not correct (won't compile) and violates "uniqueness" whether I pass the vector to the constructor by value or by reference. I want Foo to be the new owner of the vector, and want the calling function to relinquish ownership. Do I need the constructor to take a std::unique_ptr<std::vector<std::unique_ptr<int> > > to do this?

Foo.h

class Foo
{
public:
  Foo(vector<std::unique_ptr<int> > vecOfIntPtrsOwnedByCaller);

private:
  vector<std::unique_ptr<int> > _vecOfIntPtrsOwnedByFoo;
}

Foo.cpp

Foo::Foo(std::vector<std::unique_ptr< int> > vecOfIntPtrsOwnedByCaller)
{
    _vecOfIntPtrsOwnedByFoo = vecOfIntPtrsOwnedByCaller;
}

Any help would be much appreciated - I've scoured the net looking for the correct way to do this. Thanks!

1
std::vector<std::unique_ptr<int>> requires either a temporary, or the caller to use std::move on their own vector, since it can't just be copied in. If you want to make it really explicit, consider std::vector<...>&&. After that, move into your member (in the member-initializers!) with std::move.Xeo
Do you want the vector to be uniquely-owned, or the elements contained therein?Lightness Races in Orbit
Lightness Races in Orbit - Both.Jen
Xeo - thanks for your response. I am just reading on rvalue references now. If I understand you correctly, the constructor should take an rvalue reference to a vector and do _vecOfIntPtrsOwnedByFoo(std::move(vecOfIntPtrsRvalRefence)). I am not entirely clear how to make the vector passed into the constructor a temporary/rvalue. The vector I am trying to pass to the constructor is a global variable of another class.Jen

1 Answers

27
votes

std::unique_ptr<T> is a non-copyable but movable type. Having a move-only type in a std:vector<T> make the std::vector<T> move-only, too. To have the compiler automatically move objects, you need to have an r-value for move-construction or move-assignment. Within your constructor the object vecOfIntPtrsOwnedByCaller is an l-value, although one which, despite its name, already owns the pointed to ints: they got "stolen" from the caller when the caller created the object. To move from an l-value, you need to use std::move() (or something equivalent):

Foo::Foo(std::vector<std::unique_ptr<int>> vecOfIntPtrsOwnedByCaller)
{
    _vecOfIntPtrsOwnedByFoo = std::move(vecOfIntPtrsOwnedByCaller);
}

or, preferable

Foo::Foo(std::vector<std::unique_ptr<int>> vecOfIntPtrsOwnedByCaller)
    : _vecOfIntPtrsOwnedByFoo(std::move(vecOfIntPtrsOwnedByCaller))
{
}

The latter approach avoid first default-constructing the member and then move-assigning to it and, instead, move-constructs the member directly. I guess, I would also make the argument an r-value reference but this isn't necessary.

Note, that you can construct objects of type Foo only from something which can be bound to an r-value, e.g.:

int main() {
    Foo f0(std::vector<std::unique_ptr<int>>()); // OK
    std::vector<std::unique_ptr<int>> v;
    Foo f1(v); v// ERROR: using with an l-value
    Foo f2{v}; v// ERROR: using with an l-value
    Foo f3 = v; // ERROR: using with an l-value
    Foo f4(std::move(v)); // OK: pretend that v is an r-value
}