Skip to content

Commit

Permalink
Merge pull request abseil#104 from tituswinters/totw126
Browse files Browse the repository at this point in the history
Initial public draft for TotW 126
  • Loading branch information
tituswinters authored Nov 3, 2017
2 parents 6386110 + 24b77f6 commit 9009c44
Showing 1 changed file with 118 additions and 0 deletions.
118 changes: 118 additions & 0 deletions _posts/2017-11-23-totw-126.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: "Tip of the Week #126: `make_unique` is the new `new`"
layout: tips
sidenav: side-nav-tips.html
published: false
permalink: tips/126
type: markdown
order: "126"
---

Originally posted as totw/126 on 2016-12-12

By James Dennett [([email protected])](mailto:[email protected]) based on a
mailing list post by Titus Winters [([email protected])](mailto:[email protected])

As a codebase expands it is increasingly difficult to know the details of
everything you depend on. Requiring deep knowledge doesn't scale: we have to
rely on interfaces and contracts to know that code is correct, both when writing
and when reviewing. In many cases the type system can provide those contracts in
a common fashion. Consistent use of type system contracts makes for easier
authoring and reviewing of code by identifying places where there are
potentially risky allocations or ownership transfers for objects allocated on
the heap.

While in C++ we can reduce the need for dynamic memory allocation by using plain
values, sometimes we need objects to outlive their scope. C++ code should prefer
smart pointers (most commonly `std::unique_ptr`) instead of raw pointers when
dynamically allocating objects. This provides a consistent story around
allocation and ownership transfer, and leaves a clearer visual signal where
there's code that needs closer inspection for ownership issues. The side effect
of matching how allocation works in the outside world post-C++14 and being
exception safe is just icing.

Two key tools for this are `absl::make_unique()` (a C++11 implementation of
C++14's `std::make_unique()`, for leak-free dynamic allocation) and
`absl::WrapUnique()` (for wrapping owned raw pointers into the corresponding
`std::unique_ptr` types). They can be found in
[absl/memory/memory.h](https://github.com/abseil/abseil-cpp/blob/master/absl/memory/memory.h).

## Why Avoid `new`?

Why should code prefer smart pointers and these allocation functions over raw
pointers and `new`?

1. When possible, ownership is best expressed in the type system. This allows
reviewers to verify correctness (absence of leaks and of double-deletes)
almost entirely by local inspection. (In code that is exceptionally
performance sensitive, this may be excused: while cheap, passing
`std::unique_ptr` across function boundaries by value has non-zero overhead
because of ABI constraints. That's rarely important enough to justify
avoiding it.)

2. Somewhat like the reasoning for preferring `push_back()` over
`emplace_back()` ([TotW 112](/tips/112)), `absl::make_unique()` directly
expresses the intent and can only do one thing (do the allocation with a
public constructor, returning a `std::unique_ptr` of the specified
type). There's no type conversion or hidden behavior. `absl::make_unique()`
does what it says on the tin.

3. The same could be achieved with `std::unique_ptr<T> my_t(new T(args));` but
that is redundant (repeating the type name `T`) and for some people there's
value in minimizing calls to `new`. More on this in \#5.

4. If all allocations are handled via `absl::make_unique()` or factory calls,
that leaves `absl::WrapUnique()` for the implementation of those factory
calls, for code interacting with legacy methods that don't rely on
`std::unique_ptr` for ownership transfer, and for rare cases that need to
dynamically allocate with aggregate initialization (`absl::WrapUnique(new
MyStruct{3.141, "pi"})`). In code review it's easy to spot the
`absl::WrapUnique` calls and evaluate "does that expression look like an
ownership transfer?" Usually it's obvious (for example, it's some factory
function). When it's not obvious, we need to check the function to be sure
that it's actually a raw-pointer ownership transfer.

5. If we instead rely mostly on the constructors of `std::unique_ptr`, we see
calls like: \
`std::unique_ptr<T> foo(Blah());` \
`std::unique_ptr<T> bar(new T());` \
It takes only a moment's inspection to see that the latter is safe (no leak,
no double-delete). The former? It depends: if `Blah()` is returning a
`std::unique_ptr`, it's fine, though in that case it would be more obviously
safe if written as \
`std::unique_ptr<T> foo = Blah();` \
If `Blah()` is returning an ownership-transferred raw pointer, that's also
fine. If `Blah()` is returning just some random pointer (no transfer), then
there's a problem. Reliance on `absl::make_unique()` and `absl::WrapUnique()`
(avoiding the constructors) provides an additional visual clue for the
places where we have to worry (calls to `absl::WrapUnique()`, and only those).

## How Should We Choose Which to Use?

1. By default, use `absl::make_unique()` (or `std::make_shared()` for the rare
cases where shared ownership is appropriate) for dynamic allocation. For
example, instead of: `std::unique_ptr<T> bar(new T());` write `auto bar
= absl::make_unique<T>();` and instead of `bar.reset(new T());` write
`bar = absl::make_unique<T>();`

2. In a factory function that uses a non-public constructor, return a
`std::unique_ptr<T>` and use `absl::WrapUnique(new T(...))` in the
implementation.

3. When dynamically allocating an object that requires brace initialization
(typically a struct, an array, or a container), use `absl::WrapUnique(new
T{...})`.

4. When calling a legacy API that accepts ownership via a `T*`, either allocate
the object in advance with `absl::make_unique` and call `ptr.release()` in
the call, or use `new` directly in the function argument.

5. When calling a legacy API that returns ownership via a `T*`, immediately
construct a smart pointer with `WrapUnique` (unless you're immediately
passing the pointer to another legacy API that accepts ownership via a
`T*`).

## Summary

Prefer `absl::make_unique()` over `absl::WrapUnique()`, and prefer
`absl::WrapUnique()` over raw `new`.

0 comments on commit 9009c44

Please sign in to comment.