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 DeclarationsOriginally posted as TotW #171 on November 8, 2019
By Hyrum Wright
Updated 2020-04-06
Quicklink: abseil.io/tips/171
Sentinel values are values that have special meaning in a specific context. For example, consider the following API:
// Returns the account balance, or -5 if the account has been closed. int AccountBalance();
Every value of int
is documented to be a valid return value for
AccountBalance
, except for -5
. Intuitively, this feels a bit odd: should
callers only check against -5
specifically, or is any negative value a
reliable “account closed” signal? What happens when the system supports negative
balances and the API needs to be adjusted to return negative values?
Using sentinel values increases the complexity of the calling code. If the caller is rigorous, it explicitly checks against the sentinel value:
int balance = AccountBalance(); if (balance == -5) { LOG(ERROR) << "account closed"; return; } // use `balance` here
Some callers may check against a broader range of values than is specified:
int balance = AccountBalance(); if (balance <= 0) { LOG(ERROR) << "where is my account?"; return; } // use `balance` here
And some callers may just ignore the sentinel value altogether, assuming that it doesn’t actually occur in practice:
int balance = AccountBalance(); // use `balance` here
The above example illustrates some of the common problems with using sentinel values. Others include:
Forgetting to check for specified sentinel values is a common bug. In the best case, the use of an unchecked sentinel value will immediately crash the system during runtime. More frequently, an unchecked sentinel value may continue to propagate through the system, producing bad results as it goes.
std::optional
InsteadUse std::optional
to indicate unavailable or invalid information instead of
using special values.
// Returns the account balance, or std::nullopt if the account has been closed. std::optional<int> AccountBalance();
The caller of our new version of AccountBalance()
now must explicitly look
inside the returned value for a potential balance, signalling that the result
might be invalid in the process. Barring additional documentation, the caller
can assume that any valid int
value can be returned from this function,
without excluding specific sentinel values. This simplification clarifies the
intent of calling code.
std::optional<int> balance = AccountBalance(); if (!balance.has_value()) { LOG(ERROR) << "Account doesn't exist"; return; } // use `*balance` here
Next time you are tempted to use a sentinel value within your system, strongly
consider using an appropriate std::optional
instead.