4
votes

I have strange issue with std::list of unique_ptr's.

Class slFlyingMonster is derived from class slMonster.

Following code works:

std::unique_ptr<slMonster> ptr(new slFlyingMonster(md));

But this code:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.push_back(new slFlyingMonster(md));

throws error:

"Error 1 error C2664: 'void
std::list>,std::allocator>>>::push_back(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : cannot convert argument 1 from 'slFlyingMonster *' to 'std::unique_ptr> &&'"

While I understand, that something is wrong, like std::list.push_back() is not the same as =, but I cannot figure out how to correctly add new class as unique_ptr to list. Any suggestions would be very welcome.

3

3 Answers

5
votes

Use push_back when you have an object of the type which your list contains, and you want to push its copy. Normally, if you don't have such an object yet (in your case, you don't), you're better off initialising a new object directly in the list — using emplace_back instead:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.emplace_back(new slFlyingMonster(md));

However, as @SebastianRedl correctly pointed out in the comments, the above has a problem of not being exception-safe. If the internal allocation of a new node inside std::list throws, the new slFlyingMonster instance would be leaked. emplace_back is not the correct choice when one of the arguments is an unprotected resource (such as a raw pointer owning memory).

So you actually want to construct a wrapper smart pointer and push it into the list. In C++14, you can do this with std::make_unique:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.push_back(std::make_unique<slFlyingMonster>(md));

With plain C++11, you can either implement your own make_unique, or explicitly create the smart pointer:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.emplace_back(std::unique_ptr<slMonster>(new slFlyingMonster(md)));
5
votes

You may use emplace_back:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.emplace_back(new slFlyingMonster(md));

or push_back a std::make_unique:

std::list<std::unique_ptr<slMonster>> mMonsters;
mMonsters.push_back(std::make_unique<slFlyingMonster>(md));

or std::move of a std::unique_ptr

std::list<std::unique_ptr<slMonster>> mMonsters;
std::unique_ptr<slMonster> p(new slFlyingMonster(md));
mMonsters.push_back(std::move(p));

The constructor std::unique_ptr<T>(T*) is explicit, so T* cannot construct implicitly a std::unique_ptr.

0
votes

Use mMonsters.emplace_back so the object is created from the argument given in parameters.