string_viewoperator+ vs. StrCat()absl::Statusstd::bindabsl::optional and std::unique_ptrabsl::StrFormat()make_unique and private Constructors.boolexplicit= delete)switch Statements Responsibly= deleteAbslHashValue and Youcontains()std::optional parametersif and switch statements with initializersinline Variablesstd::unique_ptr Must Be MovedAbslStringify()vector.at()auto for Variable Declarationsabsl::optional and std::unique_ptrOriginally posted as totw/123 on 2016-09-06
By Alexey Sokolov ([email protected]) and Etienne Dechamps ([email protected])
This tip discusses several ways of storing values. Here we use class member variables as an example, but many of the points below also apply to local variables.
#include <memory>
#include "absl/types/optional.h"
#include ".../bar.h"
class Foo {
...
private:
Bar val_;
absl::optional<Bar> opt_;
std::unique_ptr<Bar> ptr_;
};
This is the simplest way. val_ is constructed and destroyed at the beginning
of Foo’s constructor and at the end of Foo’s destructor, respectively. If
Bar has a default constructor, it doesn’t even need to be initialized
explicitly.
val_ is very safe to use, because its value can’t be null. This removes a
class of potential bugs.
But bare objects are not very flexible:
val_ is fundamentally tied to the lifetime of its parent
Foo object, which is sometimes not desirable. If Bar supports move or
swap operations, the contents of val_ can be replaced using these
operations, while any existing pointers or references to val_ continue
pointing or referring to the same val_ object (as a container), not to the
value stored in it.Bar’s constructor need to be
computed inside the initializer list of Foo’s constructor, which can be
difficult if complicated expressions are involved.absl::optional<Bar>This is a good middle ground between the simplicity of bare objects and the
flexibility of std::unique_ptr. The object is stored inside Foo but, unlike
bare objects, absl::optional can be empty. It can be populated at any time by
assignment (opt_ = ...) or by constructing the object in place
(opt_.emplace(...)).
Because the object is stored inline, the usual caveats about allocating large
objects on the stack apply, just like for a bare object. Also be aware that an
empty absl::optional uses as much memory as a populated one.
Compared to a bare object, absl::optional has a few downsides:
std::unique_ptr<Bar>This is the most flexible way. The object is stored outside of Foo. Just like
absl::optional, a std::unique_ptr can be empty. However, unlike
absl::optional, it is possible to transfer ownership of the object to
something else (through a move operation), to take ownership of the object from
something else (at construction or through assignment), or to assume ownership
of a raw pointer (at construction or through ptr_ = absl::WrapUnique(...), see
TotW 126.
When std::unique_ptr is null, it doesn’t have the object allocated, and
consumes only the size of a pointer1.
Wrapping an object in a std::unique_ptr is necessary if the object may need to
outlive the scope of the std::unique_ptr (ownership transfer).
This flexibility comes with some costs:
Bar, or something derived from
Bar). However, it may also decrease the cognitive load, as the reader
can focus only on the base interface held by the pointer.absl::optional where object
construction and destruction occur, because ownership of the object can
be transferred.absl::optional, there is a risk of accessing an object which does
not exist - the famous null pointer dereference.std::unique_ptr<Bar> is not copyable even if Bar is. This also prevents
Foo from being copyable.As always, strive to avoid unnecessary complexity, and use the simplest thing
that works. Prefer bare object, if it works for your case. Otherwise, try
absl::optional. As a last resort, use std::unique_ptr.
Bar |
absl::optional<Bar> |
std::unique_ptr<Bar> |
|
|---|---|---|---|
| Supports delayed construction | ✓ | ✓ | |
| Always safe to access | ✓ | ||
Can transfer ownership of Bar |
✓ | ||
Can store subclasses of Bar |
✓ | ||
| Movable | If Bar is movable |
If Bar is movable |
✓ |
| Copyable | If Bar is copyable |
If Bar is copyable |
|
| Friendly to CPU caches | ✓ | ✓ | |
| No heap allocation overhead | ✓ | ✓ | |
| Memory usage | sizeof(Bar) |
sizeof(Bar) + sizeof(bool) |
sizeof(Bar*) when null, sizeof(Bar*) + sizeof(Bar) otherwise |
| Object lifetime | Same as enclosing scope | Restricted to enclosing scope | Unrestricted |
Call f(Bar*) |
f(&val_) |
f(&opt_.value()) or f(&*opt_) |
f(ptr_.get()) or f(&*ptr_) |
| Remove value | N/A | opt_.reset(); or opt_ = absl::nullopt; |
ptr_.reset(); or ptr_ = nullptr; |