I’ve been using auto_ptrs before they were available in C++ (I made my own). I wrote several articles about resource management, in which auto_ptr played a prominent role. auto_ptr had always had many flaws, but it was a workhorse of memory management in a language that shuns garbage collection. Many of the flaws have been understood over the years and, in the 0x version of C++, auto_ptr was supplanted by the new improved unique_ptr. Using the latest features of C++, like rvalue references, unique_ptr implements move semantics in a consistent manner. You can now store unique_ptrs in most containers and apply many algorithms to them.
So why am I not jumping for joy? Because I know how much more can be done.
But first let me summarize the idea behind the unique pointer. It is a (smart) pointer that is the only reference to the object it’s pointing to. In particular, you cannot make a copy of a unique pointer–if you could, you’d end up with two references to the same object. You can only move it (hence the move semantics), making the source of the move invalid. With the older auto_ptr the moving was done quietly during assignment or pass-by-value. The problem with that arrangement was that the source auto_ptr would suddenly turn to null, which was sometimes confusing and occasionally led to access violations–as in this example:
void f(auto_ptr<Foo> foo); // pass by value auto_ptr<Foo> pFoo (new Foo()); f(pFoo); // pass by value nulls the internal pointer pFoo->method(); // access violation
The improvement provided by unique_ptr is to require an explicit move, to sensitize the programmer to the fact that the source of move becomes invalid. Still, the following code will compile, although the bug is much more prominent:
void f(unique_ptr<Foo> foo);
unique_ptr<Foo> pFoo (new Foo())
f(move(pFoo)); // explicit move
pFoo->method(); // access violation
A bigger problem is that there is no guarantee that a unique_ptr indeed stores the unique reference to an object. To begin with, unique_ptr can be constructed from any pointer. That pointer might have already been aliased, as in:
void f(unique_ptr<Foo> foo) {
// destructor of foo called at the end of scope
}
Foo * foo = new Foo();
unique_ptr<Foo> upFoo(foo);
// foo now aliases the contents of upFoo
f(move(upFoo)); // explicit move
foo->method(); // undefined behavior
There is also an obvious backdoor in the form of the method unique_ptr::get, which can be used to spread new aliases. This can be particularly insidious when you have to pass the result of get to a foreign function:
void f(Foo * pf) {
globalFoo = pf; // creates a global alias
}
unique_ptr<Foo> pFoo(new Foo());
f(pFoo.get()); // leaks an alias
Finally, it’s possible to create aliases to data members of the object stored in unique_ptr, as well as assign aliased references to its data members. Consider this example:
class Foo {
public:
~Foo() { delete _bar; }
Bar * _bar;
};
Bar * pBar = new Bar();
unique_ptr<Foo> upFoo(new Foo());
pFoo->_bar = pBar;
// pBar is now an alias to a member of Foo
upFoo.reset(); // deletes _bar inside foo
pBar->method(); // undefined behavior
All this adds up to quite a number of ways to shoot yourself in the foot! Still, if the only problems were deterministic access violations, I could live with them (I’m a very disciplined programmer). They are reasonably easy to reproduce and can be debugged using standard methods (code coverage). But now there is a new elephant in the room–multithreading.
The beauty of unique objects is that they can be safely passed (moved) between threads and they never require locking. Indeed, since only one thread at a time can reference them, there is no danger of data races. Except when, as in the examples above, aliases are inadvertently leaked from unique_ptr. Accessing an object through such aliases from more than one thread without locking is a very nasty bug, usually very hard to reproduce.
Type system approach
Okay, so what’s the panacea? I’m glad you asked–the type system, of course! Make unique a type qualifier and all your troubles are gone. Notice that the compiler has no idea about the semantics of some library class called unique_ptr, but it can be made aware of the semantics of the unique type qualifier. What’s more, it can enforce this semantics from cradle to grave–from construction to destruction. (I know this proposal has no chance to be incorporated into C++, but it might be useful in, let’s say, the D programming language.)
You don’t construct a unique pointer from an arbitrary pointer. You just construct the object as unique (I’m still using C++-like syntax):
unique Foo * foo = new unique Foo();
Now that the compiler knows the meaning of unique, it can, for instance, detect if a unique pointer is accessed after having been moved. The compiler might prevent such an invalid pointer from being dereferenced or passed to other functions. Such bugs can be detected at compile time, rather than through access violations at runtime.
Do we need separate syntax for move? It might seem that using the assignment syntax (the equal sign) wouldn’t be so bad, as long as the compiler prevents us from dereferencing the null pointer left behind after a move. However, another problem arises when you start instantiating templates with unique pointers. Some containers and algorithms will work out of the box. Some will refuse to be instantiated because internally they try to access unique pointers after the move. But there is a third category of templates that, although move-correct, will generate unexpected results–like vectors with null unique pointers. Unfortunately, there are situations where the compiler has limited vision (i.e., it cannot do whole-program analysis) and can miss a null unique pointer dereference. For instance, it can’t prevent the use of a vector after a random element has been moved out of it.
Let’s assume that we have a special syntax for move, say := . Here’s a simple example of its use:
unique * Foo foo1 = new unique Foo();
unique * Foo foo2 := foo1; // move
foo1->method(); // compile-time error: foo1 invalid after move
In C++0x, a lot of containers and algorithms have been rewritten to use the explicit move instead of the assignment. It wouldn’t be a big deal to use := instead. Notice that the compiler would not allow the use of a regular assignment on unique pointers (unless they are rvalues), so the templates that are not ready for move semantics would refuse to get instantiated with unique template parameters.
Obviously, the move operator applied to a non-unique pointer must also work, otherwise we’d have to write separate templates for unique and non-unique template arguments.
We also have to use our special notation when passing unique arguments to functions, as in:
void f(unique * Foo);
unique * Foo foo = new unique Foo();
f(:= foo); // move
foo->method(); // compile-time error: foo invalid after move
When returning an lvalue unique pointer (for instance a member of a data structure), we use the notation return := foo;. The move operator may be elided when returning a local unique pointer, since the source ceases to exist upon the return (in general, move is implied when the source is a unique rvalue).
The biggest strength of the type-based approach is that the compiler can reliably prevent the aliasing of unique pointers. An assignment (as opposed to move) of an (lvalue) unique pointer to either unique or non-unique pointer is impossible. Taking the address of a unique pointer is forbidden too.
That leaves us with an interesting dilemma: How do you call a (library) function that takes a regular pointer if all you have at your disposal is a unique pointer? C++ unique_ptr let’s you do it through its method get. But we know how dangerous get is as far as aliasing goes.
If you don’t have the source code for the function you are calling, you probably shouldn’t be calling it with a unique pointer, because you have no idea what this function will do with it (store an alias in a global variable?). If you know the implementer of the function and he/she is willing to donate his/her kidney in case the function leaks aliases, you may risk casting away the uniqueness.
There is however a way for a function to guarantee that it’s alias-safe by declaring its parameters lent. The compiler will check the body of the function to make sure it doesn’t leak aliases to any part of the lent parameter, nor does it store non-unique (and possibly aliased) objects inside the lent object. Of course, the function can only pass the lent parameter (or any sub-part of it) to another function that makes the same guarantee.
It’s not obvious which should be the default: lent or it’s opposite, owned. If lent were the default, the compiler would be flagging a lot of valid single-threaded code (although it makes sense to assume that methods of a monitor object take lent parameters by default).
The relationship between unique and lent is very similar to that between immutable and const in D. If you declare data as unique or immutable you can safely pass it to a functions that declares its parameter as lent or const, respectively. lent guarantees that the parameter will not be aliased, const that it won’t be mutated.
Speaking of immutable–there’s always been a problem with constructing non-trivial immutable objects. The tricky part is that during construction we often need to explicitly modify the object, but we don’t want to leak non-const aliases to it or to its sub-parts. And now we have a new tool at hand to control aliasing–unique pointers. Instead of constructing an immutable object, you may construct a unique object, with all the guarantees about aliasing, and then move it to an immutable pointer. Just like this:
unique Foo * pFoo = new unique Foo();
immutable Foo * imFoo := pFoo;
(Of course, C++ doesn’t have immutable types either, but D does.)
By the way, you can always safely move a unique pointer to any of immutable, const, or regular pointers. Notice that it doesn’t mean that unique is a supertype of, for instance, immutable. You can’t pass unique where immutable is expected (you don’t want read-only aliases to escape to another thread!)–you can only move it.
A method that promises not to leak aliases to this is declared as lent. The compiler will detect any violations of this promise, as in:
class Foo {
Bar * _bar;
public:
Bar * get() lent {
return _bar; // error: a lent method returning an alias
}
};
In general, when you are dealing with a unique object, you may only call its lent methods.
Issues
Strict uniqueness imposes constraints on the internal structure of objects. Imagine creating a unique doubly-linked list. A link in such a list must be accessible from two places: from the previous link and from the next link. The scheme that absolutely prevents the creation of aliases to unique objects will not allow the creation of doubly-linked lists–and rightly so! Imagine moving a link out of the list: you’ll end up with a null pointer in the list, and a possible cross-thread aliasing if the (unique) link migrates to another thread.
There is a solution to this problem based on the ownership scheme (like the one used in Guava or GRFJ), which allows cross-aliasing of objects that share the same owner (e.g., all links are owned by the list). Such aliases cannot be leaked because the compiler won’t allow objects owned by a unique object to be moved. But that’s a topic for another blog post.
The use of iterators on unique containers is also highly restricted, but there are other ways of iterating that are inherently more thread-safe.
Conclusion
Any extension to the type system is usually met with some resistance from the user community. While some appreciate the added safety, others complain about added complexity. So it’s very important to be able to limit this complexity to specific areas.
When are unique and lent qualifiers really needed? In C++, at least in my experience, unique_ptr serves mostly as a poor man’s garbage collector. Since memory management permeates all C++ programs, the switch to using unique qualifiers would require a lot of changes even in single-threaded programs. In contrast, in garbage-collected languages, the main usefulness of unique is for multithreaded programming. A message queue that passes large unique objects between threads is more efficient than the one that deep-copies them. And unique objects never require locking.
When multithreading is involved, a safer type system doesn’t really add complexity. It translates the hard complexity of shared-memory concurrency into something that is more approachable by mere mortals. |
As always, the introduction of new type modifiers may lead to code duplication (as in the case of const and non-const accessors). This problem can be dealt with using qualifier polymorphism–a topic for a whole new blog post.
I understand that this post might be hard to follow. That doesn’t mean that the uniqueness scheme is difficult to understand. My task was not only to explain the hows, but also the whys–and that requires giving a lot of background.
Bibliography
- Jonathan Aldrich, Valentin Kostadinov, Craig Chambers, Alias Annotations for Program Understanding. Describes unique and lent annotations in detail.
- See also my previous blog on Generic Race-Free Java.
May 21, 2009 at 3:35 pm
“That leaves us with an interesting dilemma: How do you call a (library) function that takes a regular pointer if all you have at your disposal is a unique pointer? C++ unique_ptr let’s you do it through its method get. But we know how dangerous get is as far as aliasing goes.
If you don’t have the source code for the function you are calling, you probably shouldn’t be calling it with a unique pointer, because you have no idea what this function will do with it (store an alias in a global variable?).”
In other words, your “unique” proposal doesn’t actually solve this problem.
May 21, 2009 at 5:32 pm
There is no “cheap” solution, especially if you’re dealing with a lot of legacy code. There was a similar problem when const was introduced. If you have a const object, you can’t call a function that takes a regular pointer. The solution is to annotate the function parameter as “const”. With unique, the necessary annotation is “lent”.
May 22, 2009 at 12:16 am
“Now that the compiler knows the meaning of unique, it can, for instance, detect if a unique pointer is accessed after having been moved.”
This is equivalent to solving the halting problem and is thus patently false. You can only detect if a pointer has been moved at run-time.
May 22, 2009 at 1:34 am
As “anonymous” said, you cannot know in compile time whether a unique pointer will be moved or not. Consider:
if (condition) f(:= ptr); // now is ptr moved or not?
Furthermore, I felt all your examples before title “Type system approach” were biased, too. When working with pointers (and I rarely do, as I use references instead) you should always be certain, who owns what and how long can an object be pointed.
If there is a function with such side effect as changing a global pointer that should always be pointed out in the documentation and known at the caller side. That’s just another way to shoot yourself in the foot, but it’s so not because of the unique_ptr’s but because of C’s or C++’s low level in general. The following code will leak an alias as well:
void f(Foo * pf) {
globalFoo = pf; /* creates a global alias */
}
{
Foo foo;
f(&foo); // leaks an alias
}
May 22, 2009 at 4:47 am
I don’t think that code like
unique_ptr<int> p = source();
sink(move(p));
cout << *p << endl;
is an issue. As for aliases: Well, for now you have to rely on a function’s documentation w.r.t. “pointer leakage”. I agree that a type system solution would have been nice. For example, the pointer returned by unique_ptr::get could have some sort of “scoped” qualifier to prevent “reference-escaping”. For compatibility with legacy code you could cast it away with a const_cast.
On the other hand I don’t like the idea of adding more qualifiers to the type system. It’ll probably make some generic / meta programming code more complicated as more cases have to be considered (for example: see member function wrappers std::mem_fun which requires 4 cases for every const/volatile member function qualification combination). Instead you could try to enforce some coding convention w.r.t. documentation and function parameter types which minimizes those errors.
Maybe some additional type checking via C++0x attribues is viable …
May 22, 2009 at 10:19 am
pyrtsa,
You mention:
if (condition) f(:= ptr); // now is ptr moved or not?
Couldn’t the compiler generate a error similar to the warning you get (on some smarter compilers) when doing:
void g(int i);
void f(bool b) {
int a;
if (b) a = 0;
g(a); // warning: use of potentially uninitialized value
}
e.g.:
template void g(T * i);
template void h(T * i);
template void f(bool b, T unique * a) {
if (b) g(:= a);
h(:= a); // error: use of potentially moved pointer
}
template void g(T unique *);
template void h(T unique *);
template void f(bool b, T unique * a) {
if (b) g(:= a);
else h(:= a); // no error, compiler can determine only one move (g or h) will happen.
}
May 22, 2009 at 10:52 am
As I said in my post, I am disciplined enough to avoid null pointer traps in single-threaded code. So the real topic of the post is multithreaded programming and the problem of aliasing. I don’t believe C++ with its universal reliance on programmer’s discipline can deal with this problem.
BTW, the detection of possible null assignments is not undecidable if you are willing to live with a few false positives. The unique/lent scheme has been proven to work in the literature I’m citing.
May 22, 2009 at 11:02 am
What are the memory fence requirements of unique? i.e. are any needed to prevent races when an unique object moves between threads? If so, could the inter-thread passing mechanism (i.e. a Compare-and-swap, lock, etc.) take care of these issues?
bicycle shed:
I like ‘lent’ over ‘scope’. (Which was also proposed in the D programming language to do something similar) It’s shorter and doesn’t cause issues with D’s current use of the scope keyword.
I prefer ‘mobile’ from CSP/Occam to C++0X’s ‘unique’. In the context of multi-threaded programs, mobile better conveys the intend use of this type.
May 22, 2009 at 11:21 am
This is a very good question. It turns out no additional fencing is necessary. You always move objects between threads through some shared portal. That portal provides the necessary synchronization. For instance, when thread 2 picks up a unique object from the queue, it has to lock the queue. The lock “flushes” pending writes (in this case, pending moves).
May 22, 2009 at 2:15 pm
Would something like the following be syntactically valid code?
class Foo {
Bar * _bar;
public:
void set(Bar * value) {
_bar = value;
}
};
unique Foo * f = new unique Foo();
Bar * b = new Bar();
f.set(b);
channel.send(f); // sends f to a different thread
If so, how is the race between b and _bar handled?
If not, what are the rules for setting _bar? Is any compiler magic required?
i.e. _bar = value;
v.s. _bar = new Bar();
v.s. _bar = Bar.factory();
v.s. _bar := unique_value;
v.s. _bar = new unique Bar();
v.s. _bar = other_foo._bar;
Automatically making _bar ‘owned’ ala Alias Annotations for Program Understanding, is one solution, but I’m not sure it handles the last case of _bar = other_foo._bar. The other obvious solution is to automatically make _bar unique, though that is a bit restrictive. Thoughts?
May 22, 2009 at 2:26 pm
Would it be logical to have lent ref returns? i.e. Using D syntax:
class Foo {
private:
int a = 0;
public:
lent ref int bar() lent { return a; }
lent ref Foo self() lent { return this; }
}
auto f = new unique Foo();
foo.bar++;
assert(foo.bar==1);
int* p = &foo.bar; // Error: cannot take the reference of type lent ref int.
Foo g = foo.self; // Error: cannot assign type lent Foo to type Foo
May 22, 2009 at 9:42 pm
In the first example, you can’t call set on a unique object–only lent methods may be called, and they won’t allow cross-aliasing. If you really need to re-assign _bar, you have to declare get as taking unique Bar. The caller then has to use move the argument and thus loses the alias.
You are correct about returning lent.
Also, notice that you can’t pass lent to another thread since there is no way to store it inside a channel/queue. When passing unique, you have to move it:
channel.send(:=foo);
May 23, 2009 at 5:11 pm
[…] unique_ptr–How Unique is it? I’ve been using auto_ptrs before they were available in C++ (I made my own). I wrote several articles about […] […]
May 24, 2009 at 11:55 am
Данный пост реально помог мне принять очень важное для себя решение. За что автору отдельное спасибо. Жду от Вас новых постов!
May 24, 2009 at 12:25 pm
мне очень приятно!
May 25, 2009 at 11:40 am
the comment should not be “// pass by value”; instead, the comment should be “// takes ownership”
May 25, 2009 at 2:30 pm
This might be confusing, I admit. What I meant is that auto_ptr is passed by value rather than by reference. However, since auto_ptr overloads the copy constructor, during pass-by-value the ownership of the internal object is shifted. But since the C++ language has no notion of ownership, this is indeed pass by value.
May 26, 2009 at 1:09 am
Занимательная интересная статья Да и в отличие от большинства других подобных советов воду в уши не льешь
May 26, 2009 at 9:57 am
[…] synchronized keyword is missing. The only unusual thing is the move operator, :=, introduced in my previous blog. It is needed in case we want to pass unique objects through MVar. You are probably asking […]
June 2, 2009 at 2:18 pm
[…] discussed unique objects in one of my previous posts. Although not strictly required in the ownership scheme, uniqueness allows for very efficient and […]
July 7, 2009 at 6:04 am
What about fortran 90 style of pointer
http://www.personal.psu.edu/jhm/f90/lectures/42.html ?
July 10, 2009 at 5:45 pm
[…] For a skeptics point of view, see what Bartosz Milewski has to say about […]
December 8, 2009 at 11:44 am
[…] Programming, Scala, Type System Leave a Comment I’ve blogged before about the C++ unique_ptr not being unique and how true uniqueness can be implemented in an ownership-based type system. But I’ve been […]
June 21, 2012 at 10:53 am
Your article was one of my first introductions to unique_ptr. It biased me toward this definition “It is a (smart) pointer that is the only reference to the object it’s pointing to. ” So I was concerned about the aliasing issues you mention, and trying to write code in such a way as to avoid that…
Since then I’ve noticed that the looser interpretation of unique_ptr actually seems to be the prevailing one, for instance on StackOverflow. That is: unique_ptr isn’t the only pointer to an object, it’s just the only *unique_ptr* to the object. (Perhaps it should be called unique_unique_ptr? :P) It transfers ownership but non-owning aliases are something expected.
Of course, people realize that a raw pointer via .get() is not as robust as shared_ptr’s “weak_ptr”. But I use shared_ptr when there really is no logical reason to try and pinpoint the moment of creation and death of an object’s lifetime…and Java-like semantics make sense. But unique_ptr is the right fit when there’s a decisive point of creation and deletion of an object, where you want to follow the hot-potato of ownership.
As time as passed, do you feel this is the right way to look at and use unique_ptr? Your ideas for why a language would have stronger analysis of aliasing are good, but should C++11 programmers accept a more practical reality?
June 21, 2012 at 11:27 am
@HostileFork: If anything I feel stronger about unique access. Passing around aliases to the contents of unique_ptr is morally equivalent to passing references to local variables, which we all agree is extremely dangerous. And where concurrency is involved, such style of programming is criminal.
September 19, 2013 at 12:42 pm
[…] unique_ptr–How Unique is it?, WordPress, 2009 […]
November 18, 2014 at 10:52 pm
[…] *pa, and, finally, you can call .get() on the smart pointer and leak the result (see for instance https://bartoszmilewski.com/2009/05/21/unique_ptr-how-unique-is-it/ for possible problems). I will try to address all these issues in this article. I am not sure, as […]
November 18, 2014 at 11:05 pm
Hi Bartosz,
I read this post a long time ago, but I remembered your problem of leaking pointers to owned objects. I made a humble exercise in canning objects myself, if you cared to look at, it’s at http://ljwo.wordpress.com/2014/11/18/polymorphic-objects-with-value-semantics/
Regards
Łukasz
May 19, 2020 at 11:35 pm
Funny how this blog post basically explored the same design space that (maybe Midori explored, and) Rust ended up carrying into production.
and to this day you can’t create a useful doubly linked list in safe Rust…
Honestly I don’t know about this… I’m more well-versed with Rust semantics than memory pools, or whatever Verona is doing.