Tip of the Week #65: Putting Things in their Place

Originally posted as totw/65 on 2013-12-12

By Hyrum Wright ([email protected])

“Let me ’splain. No, there is too much. Let me sum up.” –Inigo Montoya

C++11 added a new way to insert elements into standard containers: the emplace() family of methods. These methods create an object directly within a container, instead of creating a temporary object and then copying or moving that object into the container. Avoiding these copies is more efficient for almost all objects, and makes it easier to store move-only objects (such as std::unique_ptr) in standard containers.

The Old Way and the New Way

Let’s look at a simple example using vectors to contrast the two styles. The first example uses pre-C++11 code:

class Foo {
 public:
  Foo(int x, int y);
  
};

void addFoo() {
  std::vector<Foo> v1;
  v1.push_back(Foo(1, 2));
}

Using the older push_back() method, two Foo objects are constructed: the temporary argument and the object in the vector that is move-constructed from the temporary.

We can instead use C++11’s emplace_back() and only one object will be constructed directly within the memory of the vector. Since the “emplace” family of functions forward their arguments to the underlying object’s constructor, we can provide the constructor arguments directly, obviating the need to create a temporary Foo:

void addBetterFoo() {
  std::vector<Foo> v2;
  v2.emplace_back(1, 2);
}

Using Emplace Methods for Move-Only Operations

So far, we’ve looked at cases where emplace methods improve performance, but they also make previously impossible code feasible, such as storing move-only types like std::unique_ptr within containers. Consider this snippet:

std::vector<std::unique_ptr<Foo>> v1;

How would you insert values into this vector? One way would be to use push_back() and construct the value directly within its argument:

v1.push_back(std::unique_ptr<Foo>(new Foo(1, 2)));

This syntax works, but can be a bit unwieldy. Unfortunately, the traditional way of getting around this confusion is fraught with complexity:

Foo *f2 = new Foo(1, 2);
v1.push_back(std::unique_ptr<Foo>(f2));

This code compiles, but it leaves ownership of the raw pointer unclear until the insertion. What’s worse, the vector now owns the object, but f2 still remains valid, and could accidentally be deleted later on. To an uninformed reader, this ownership pattern can be confusing, particularly if construction and insertion are not sequential events as above.

Other solutions won’t even compile, because unique_ptr isn’t copyable:

std::unique_ptr<Foo> f(new Foo(1, 2));
v1.push_back(f);             // Does not compile!
v1.push_back(new Foo(1, 2)); // Does not compile!

Using emplace methods can make it more intuitive to insert the object while it’s being created. In other cases, if you need to move the unique_ptr into the vector, you can:

std::unique_ptr<Foo> f(new Foo(1, 2));
v1.emplace_back(new Foo(1, 2));
v1.push_back(std::move(f));

By combining emplace with a standard iterator, you can also insert the object at an arbitrary location in the vector:

v1.emplace(v1.begin(), new Foo(1, 2));

That said, in practical terms we wouldn’t want to see these ways to construct a unique_ptr - Use std::make_unique (from C++14) or absl::make_unique (if you’re still on C++11).

Conclusion

We’ve used vector as an example in this Tip, but emplace methods are also available for maps, lists and other STL containers. When combined with unique_ptr, emplace allows for good encapsulation and makes the ownership semantics of heap-allocated objects clear in ways that weren’t possible before. Hopefully this has given you a feel for the power of the new emplace family of container methods, and a desire to use them where appropriate in your own code.


Subscribe to the Abseil Blog