So Can Unique_Ptr Be Used Safely in Stl Collections

So can unique_ptr be used safely in stl collections?

I think it's more a question of philosophy than technic :)

The underlying question is what is the difference between Move and Copy. I won't jump into technical / standardista language, let's do it simply:

  • Copy: create another identical object (or at least, one which SHOULD compare equal)
  • Move: take an object and put it in another location

As you said, it is possible to implement Move in term of Copy: create a copy into the new location and discard the original. However there are two issues there. One is of performance, the second is about objects used for RAII: which of the two should have ownership ?

A proper Move constructor solves the 2 issues:

  • It is clear which object has ownership: the new one, since the original will be discarded
  • It is thus unnecessary to copy the resources pointed to, which allows for greater efficiency

The auto_ptr and unique_ptr are a very good illustration of this.

With an auto_ptr you have a screwed Copy semantic: the original and the copy don't compare equal. You could use it for its Move semantic but there is a risk that you'll lose the object pointed to somewhere.

On the other hand, the unique_ptr is exactly that: it guarantees a unique owner of the resource, thus avoiding copying and the inevitable deletion issue that would follow. And the no-copy is guaranteed at compile-time too. Therefore, it's suitable in containers as long as you don't try to have copy initialization.

typedef std::unique_ptr<int> unique_t;
typedef std::vector< unique_t > vector_t;

vector_t vec1; // fine
vector_t vec2(5, unique_t(new Foo)); // Error (Copy)
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
// Courtesy of sehe

std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)

So you can use unique_ptr in a container (unlike auto_ptr), but a number of operations will be impossible because they involve copying which the type does not support.

Unfortunately Visual Studio may be quite lax in the enforcement of the standard and also has a number of extensions that you would need to disable to ensure portability of the code... don't use it to check the standard :)

Why unique_ptr works but auto_ptr doesn’t with STL

You're looking at the whole thing backwards.

In C++98/03, we got auto_ptr. This type lies to everyone by pretending that it supports copy semantics when in fact copying it does something very much unlike a copy operation. Therefore, any type which relies on a type providing copy semantics, like certain containers, would not take well to getting an auto_ptr. Of course, you will only find out when your code becomes dysfunctional, not at compile time.

In C++11, we got unique_ptr, a type which explicitly does not provide copy semantics. Instead, it provides move semantics, and provides them correctly. Therefore, any type which relies on a type providing copy semantics will fail to compile when given a unique_ptr.

However, the reason unique_ptr came into being at all because the concept of moving an object was added to the language in C++11. When new concepts get added to the language, existing tools, like standard library requirements, are often re-evaluated relative to that language feature.

For example, types that formerly required copy semantics did not necessarily have to keep that requirement. C++98/03 containers that required copy semantics were updated in C++11 to only require (noexcept) move semantics from a type.

So it's not that unique_ptr fulfills some requirement that auto_ptr did not. It's that the language changed to no longer need that requirement, but auto_ptr was still lying about what it did, so for backwards compatibility sake, we created a new type that respected the new language features and didn't lie to people.

When does it make sense to use unique_ptr with STL containers? (C++11)

for (auto i : v5) {
i->go();
}

Should be

for (auto& i : v5) { // note 'auto&'
i->go();
}

Else you'll try to copy the current element.

Also, you can't use an initializer list like that, because the constructors of std::unique_ptr and std::shared_ptr are marked explicit. You need to do something like this:

#include <iterator> // make_move_iterator, begin, end

template<class T>
std::unique_ptr<T> make_unique(){ // naive implementation
return std::unique_ptr<T>(new T());
}

std::unique_ptr<Base> v1_init_arr[] = {
make_unique<Derived>(), make_unique<Derived>(), make_unique<Derived>()
};

// these two are only for clarity
auto first = std::make_move_iterator(std::begin(v1_init_arr));
auto last = std::make_move_iterator(std::end(v1_init_arr));
std::vector<std::unique_ptr<Base>> v1(first, last);

std::vector<std::shared_ptr<Base>> v2 = {
std::make_shared<Derived>(),
std::make_shared<Derived>(),
std::make_shared<Derived>()
};

And this is a Good Thing™, because otherwise you might leak memory (if one of the later constructors throws, the former ones aren't yet bound to the smart pointers). The tip-toeing for the unique_ptr is necessary, because initializer lists copy their arguments, and since unique_ptrs aren't copyable, you'd get a problem.


That said, I use a std::map<std::string, std::unique_ptr<LoaderBase>> for a dictionary of loaders in one of my projects.

Using std::unique_ptr with standard containers

  1. Nothing. A unique_ptr is just a wrapper around a pointer, which deletes the pointer when the unique_ptr is destroyed. It has no overhead (just like the auto_ptr template it replaces).
  2. Nope -- it will just work. The difficulty actually comes from inserting the pointer into the vector or map -- whereas you must move the unique_ptr into the container.

C++ std::unique_ptr with STL container

I think the job your set is doing is probably different to unique_ptr.

Your set is likely recording some events, and ensuring that only 1 event is recorded for each object that triggers, using these terms very loosely.

An example might be tracing through a mesh and recording all the nodes that are passed through.

The objects themselves already exist and are owned elsewhere.

The purpose of unique_ptr is to ensure that there is only one owner for a dynamically allocated object, and ensure automatic destruction of the object. Your objects already have owners, they don't need new ones!

Returning a STL container of unique pointers

It is clearly not a good practice to put raw pointers in std containers. Your type is valid but you have somewhere an unwanted copy that is not authorized. You need to find that part of the code and force a move operation instead.

on EDIT : you probably wants to return by reference like this :

std::array<std::unique_ptr<tp::QuadTree>, 4> const & get_children() const { return children; }

If you want to extract the values and remove them from the initial member, then force a move operation :

std::array<std::unique_ptr<tp::QuadTree>, 4> extract_children(){ return std::move(children); }

How to properly return a collection of unique_ptr

I would recommend creating your own iterator class. Then create begin and end member functions. You can even overload the dereference operator to return references, instead of pointers (unless your pointers might be null). It might start something like this:

class iterator :
public std::iterator<std::random_access_iterator_tag, Object>
{
public:
Object& operator*() const { return **base; }
Object* operator->() const { return &**base; }
iterator& operator++() { ++base; return *this; }

// several other members necessary for random access iterators
private:
std::vector<ObjectUPtr>::iterator base;
};

It's a bit tedious implementing a standard conforming iterator, but I think this is by far the most idiomatic solution. As mentioned in the comments, the Boost.Iterator library, specifically boost::iterator_facade can be used to relieve some of the tedium.

Dealing with unique_ptr in containers

There is a simple solution to this.

Pass around vec[n].get() -- raw pointers. So long as you don't store them, and always get them back from the owner, and the owner doesn't destroy them while you are using them, you are safe.

If you aren't willing to follow that level of discipline, what you need is a std::shared_ptr in the vector, and pass around and store std::weak_ptrs. The weak_ptrs will auto-invalidate when the last shared_ptr goes away (and by policy, the only persistent shared_ptr is the one in the owning vector).

This has the added advantage that if you are in the middle of doing work on an element and the vector clears itself, you don't segfault. You access a weak_ptr by .lock(), which returns a shared_ptr, during whose lifetime the raw pointer is guaranteed to be good.

The downside is that this ups the cost, the upside is that it allows for weak shared ownership and lazy notification of invalidation.

std::unique_ptr and its effect on matching pointers in STL containers

Since the std::vector<std::unique_ptr<Light>> is the owner of the Lights, there is little use in returning references to these rather than pointers to the Lights. The references are only useful if you need to replace the value. I'd just travel in terms of Light* or Light const* where the objects are used rather than owned. You can get the pointer held by a std::unique_ptr<T> with the get() method.

Instead of returning a Light* you could return a simple_ptr<Light> where simple_ptr<T> essentially just has a constructor taking a T* and operators operator->() which returns a T* and operator*() which returns a T&:

template <typename T>
class simple_ptr {
T* ptr;
public:
explicit simple_ptr(T* ptr): ptr(ptr) {}
T* operator->() const { return this->ptr; }
T& operator*() const { return *this->ptr; }
};

The generated functions just do the Right Thing (i.e. copy the pointer as needed). Without jumping through hoops it wouldn't be possible delete this pointer. If you give your Light type a custom unary operator&() you could make it even harder to explicitly get to the pointer. Tt won't be impossible, though, as you can always call ptr.operator->() to get it. However, you should only defend against Murphy, not against Machiavelli.



Related Topics



Leave a reply



Submit