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/101 on 2015-07-29
By Titus Winters ([email protected])
Consider the following code snippet:
const string& name = obj.GetName();
std::unique_ptr<Consumer> consumer(new Consumer(name));
In particular, I’d like to draw your attention to the &
. Is it appropriate?
What should we check? What can go wrong? I find a decent number of C++
programmers that aren’t entirely clear on references, but do generally know that
they “avoid making copies.” As with most issues in C++, it’s more complicated
than that.
There are two (maybe three) important questions here:
GetName()
)?name
)?We’ll continue with string
as our example type, but the same arguments
generalize for most non-trivial value types.
string
, initializing string
: This is usually
RVO and is
guaranteed to be, at worst, a move for modern types (See TotW
77).string&
or const string&
, initializing string
: This is a
copy (there must be some long-living object which we are returning a
reference to, so once we initialize a new string there are two names for
that data, hence a copy. See TotW 77). Sometimes this is
valuable, like if you need your string
to outlast the lifetime guarantees
provided by the function.string
, initializing string&
: This won’t compile, as you
cannot bind a reference to a temporary.const string&
, initializing string&
: This won’t compile, as
you’ve dropped the const inappropriately.const string&
, initializing const string&
: This is no cost
(you’re effectively returning just a pointer). However, you have inherited
any existing lifetime restrictions: how long is that reference good for?
Most accessor methods that return a reference are returning a member - at
most, the reference can be valid for the life of the containing object.string&
, initializing string&
: This is the same as #5, but
with the additional caveat: the returned reference is non-const, so any
modifications to your reference will be reflected in the source.string&
, initializing const string&
: The same as #5.string
, initializing const string&
: You’d think, given #3,
that this wouldn’t work. However, the language has special-case support for
this: if you initialize a const T&
with a temporary T
, that T
(in this
case string
) is not destroyed until the reference goes out of scope (in
the common case of automatic or static variables).Scenario #8 is what allows most reflexive use of references (that is “Oh, I
don’t want to copy so I’ll just assign into a reference” without necessarily
thinking about what is being returned). However, because of #1, it’s also
not really doing anything for you: there probably wouldn’t have been a copy in
the first place. Further, now readers of your code have to contend with your
local variable being of type const string&
instead of string
, and thus worry
about whether the underlying string
has gone out of scope or changed.
Put another way: when code reviewing the original snippet, I have to worry about:
GetName()
returning by value or by reference?Consumer
take string
, const string&
or
string_view
?string
.)However, if you just declare name
as string
in the first place, it’s
generally no less efficient (because of RVO and move semantics), and at least as
likely to be safe with respect to object lifetimes.
Additionally, if there is an object lifetime issue, it’s often simpler to spot
when storing as string
: rather than looking at the interplay between the
lifetime promise of GetName()
’s returned reference and the lifetime
requirements of SetName()
, having your own string
means only having to look
at the local code and SetName()
.
All of which is to say: avoiding copies is fine, so long as you’re not making things more complicated. Making code more complicated when there wasn’t a copy in the first place is not a good tradeoff.