Tip of the Week #55: Name Counting and unique_ptr

Originally published as totw/55 on 2013-09-12

by Titus Winters ([email protected])

Updated 2017-10-20

Quicklink: abseil.io/tips/55

“Though we may know Him by a thousand names, He is one and the same to us all.” - Mahatma Gandhi

Colloquially, a “name” for a value is any value-typed variable (not a pointer, nor a reference), in any scope, that holds a particular data value. (For the spec-lawyers, if we say “name” we’re essentially talking about lvalues.) Because of std::unique_ptr’s specific behavioral requirements, we need to make sure that any value held in a std::unique_ptr only has one name.

It’s important to note that the C++ language committee picked a very apt name for std::unique_ptr. Any non-null pointer value stored in a std::unique_ptr must occur in only one std::unique_ptr at any time; the standard library is designed to enforce this. Many common problems compiling code that uses std::unique_ptr can be resolved by learning to recognize how to count the names for a std::unique_ptr: one is OK, but multiple names for the same pointer value are not.

Let’s count some names. At each line number, count the number of names alive at that point (whether in scope or not) that refer to a std::unique_ptr containing the same pointer. If you find any line with more than one name for the same pointer value, that’s an error!

std::unique_ptr<Foo> NewFoo() {
  return std::unique_ptr<Foo>(new Foo(1));
}

void AcceptFoo(std::unique_ptr<Foo> f) { f->PrintDebugString(); }

void Simple() {
  AcceptFoo(NewFoo());
}

void DoesNotBuild() {
  std::unique_ptr<Foo> g = NewFoo();
  AcceptFoo(g); // DOES NOT COMPILE!
}

void SmarterThanTheCompilerButNot() {
  Foo* j = new Foo(2);
  // Compiles, BUT VIOLATES THE RULE and will double-delete at runtime.
  std::unique_ptr<Foo> k(j);
  std::unique_ptr<Foo> l(j);
}

In Simple(), the unique pointer allocated with NewFoo() only ever has one name by which you could refer it: the name “f” inside AcceptFoo().

Contrast this with DoesNotBuild(): the unique pointer allocated with NewFoo() has two names which refer to it: DoesNotBuild()’s “g” and AcceptFoo()’s “f”.

This is the classic uniqueness violation: at any given point in the execution, any value held by a std::unique_ptr (or more generally, any move-only type) can only be referred to by a single distinct name. Anything that looks like a copy introducing an additional name is forbidden and won’t compile:

scratch.cc: error: call to deleted constructor of std::unique_ptr<Foo>'
  AcceptFoo(g);

Even if the compiler doesn’t catch you, the runtime behavior of std::unique_ptr will. Any time where you “outsmart” the compiler (see SmarterThanTheCompilerButNot()) and introduce multiple std::unique_ptr names, it may compile (for now) but you’ll get a run-time memory problem.

Now the question becomes: how do we remove a name? C++11 provides a solution for that as well, in the form of std::move().

 void EraseTheName() {
   std::unique_ptr<Foo> h = NewFoo();
   AcceptFoo(std::move(h)); // Fixes DoesNotBuild with std::move
}

The call to std::move() is effectively a name-eraser: conceptually you can stop counting “h” as a name for the pointer value. This now passes the distinct-names rule: on the unique pointer allocated with NewFoo() has a single name (“h”), and within the call to AcceptFoo() there is again only a single name (“f”). By using std::move() we promise that we will not read from “h” again until we assign a new value to it.

Name counting is a handy trick in modern C++ for those that aren’t expert in the subtleties of lvalues, rvalues, etc: it can help you recognize the possibility of unnecessary copies, and it will help you use std::unique_ptr properly. After counting, if you discover a point where there are too many names, use std::move to erase the no-longer-necessary name.


Subscribe to the Abseil Blog