forked from abseil/abseil.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request abseil#104 from tituswinters/totw126
Initial public draft for TotW 126
- Loading branch information
Showing
1 changed file
with
118 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |