string_view
operator+
vs. StrCat()
absl::Status
std::bind
absl::optional
and std::unique_ptr
absl::StrFormat()
make_unique
and private
Constructors.bool
explicit
= delete
)switch
Statements Responsibly= delete
AbslHashValue
and Youcontains()
std::optional
parametersif
and switch
statements with initializersinline
Variablesstd::unique_ptr
Must Be MovedAbslStringify()
vector.at()
auto
for Variable Declarationsbool
Originally posted as TotW #141 on January 19, 2018
Updated 2020-04-06
Quicklink: abseil.io/tips/141
Checking a pointer for null before dereferencing is important to avoid crashes and bugs. This can be done in two ways:
if (foo) { DoSomething(*foo); }
if (foo != nullptr) { DoSomething(*foo); }
Both of these conditionals have the same semantics given that foo
is a
pointer, but the type-checking on the latter is a little tighter. Many types in
C++ can be implicitly converted to bool
, and additional caution is required
when the pointed-to type can itself be converted to bool
.
Consider the following code which could have two very different meanings:
bool* is_migrated = ...; // Is this checking that `is_migrated` is not null, or was the actual // intent to verify that `*is_migrated` is true? if (is_migrated) { ... }
This code is clearer:
// Looks like a null-pointer check for a bool* if (is_migrated != nullptr) { ... }
Both styles are acceptable in Google C++ code. So when the underlying type is
not implicitly convertible to bool
, follow the style of surrounding code. If
the value in question is a “smart pointer” like std::unique_ptr
, the semantics
and tradeoffs are the same.
What about optional (e.g. std::optional
) values? They deserve more careful
consideration.
For example:
std::optional<bool> b = MaybeBool(); if (b) { ... } // What happens when the function returns std::optional(false)?
Putting the variable declaration in the conditional of the if
statement
limits the scope
of the variable, but the value is implicitly1 converted to a bool
, so it
may not be explicit which boolean property is tested.
The intent of the following code is clearer:
std::optional<bool> b = MaybeBool(); if (b.has_value()) { ... }
Note that, in fact, the code snippets above are equivalent: std::optional
’s
conversion to bool
only looks at whether the optional
is full, not at its
contents. A reader may find it counterintuitive that optional(false)
is
true
, but it’s immediately clear that optional(false)
has a value. Again,
it’s worth extra caution when the underlying type is implicitly convertible to
bool
.
One pattern for optional return values is to put a variable declaration in the
conditional of the if
statement. This
limits the scope
of the variable, but involves an implicit conversion to bool
:
if (std::optional<Foo> foo = MaybeFoo()) { DoSomething(*foo); }
Note: In C++17, if
statements can contain an initializer, so the scope of
the declaration can be limited while avoiding the implicit conversion:
if (std::optional<Foo> foo = MaybeFoo(); foo.has_value()) { DoSomething(*foo); }
Let’s say you’ve taken the advice of Tip #94 and decided to use an
enum
in your function signature instead of a bool
for better readability at
the call sites. This kind of refactoring might introduce an implicit conversion
in the function definition:
void ParseCommandLineFlags( const char* usage, int* argc, char*** argv, StripFlagsMode strip_flags_mode) { if (strip_flags_mode) { // Wait, which value was true again? ... } }
You can gain additional clarity by replacing the implicit conversion with an explicit comparison:
void ParseCommandLineFlags( const char* usage, int* argc, char*** argv, StripFlagsMode strip_flags_mode) { if (strip_flags_mode == kPreserveFlags) { ... } }
In summary, be aware that implicit conversions to bool
can be unclear, so
consider writing more explicit code:
nullptr
(especially if the pointed-at type is
implicitly convertible to bool
).std::optional<T>::has_value()
(especially if the contained type is
implicitly convertible to bool
). Use the optional initializer form for
if
to limit the scope of variables (Tip #165). Remember
call-only interfaces though, and don’t take the address of value()
or
has_value()
. The testing::Optional
matcher can help in tests.More specifically, it’s a “contextual conversion”. ↩