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_ptr
s 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
- Nothing. A
unique_ptr
is just a wrapper around a pointer, which deletes the pointer when theunique_ptr
is destroyed. It has no overhead (just like theauto_ptr
template it replaces). - 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_ptr
s. The weak_ptr
s 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 Light
s, there is little use in returning references to these rather than pointers to the Light
s. 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
Boost::Flat_Map and Its Performance Compared to Map and Unordered_Map
C++ Access Derived Class Member from Base Class Pointer
Handling Header Files Dependencies with Cmake
How to Convert Unsigned Char* to Std::String in C++
Why Aren't Static Const Floats Allowed
How to Access Private Data Members Outside the Class Without Making "Friend"S
How to Convert Utf-8 Std::String to Utf-16 Std::Wstring
C++ Class Member Function Pointer to Function Pointer
Convert a Char* to Std::String
What Does the Fpermissive Flag Do
Can Google Mock a Method with a Smart Pointer Return Type
So Can Unique_Ptr Be Used Safely in Stl Collections