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 DeclarationsboolOriginally 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”. ↩