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()
Originally posted as totw/107 on 2015-12-10
By Titus Winters ([email protected])
There have been some reports of confusion about references and lifetimes after TotW 101, so in this Tip we’ll dive more into the question, “When does reference lifetime extension apply?”
string Foo::GetName();
const string& name = obj.GetName(); // Is this safe/legal?
In short, lifetimes of temporaries (referents) will be extended if and only if
const T&
(or T&&
, although Google style generally ignores that)
is initialized to the result of an expression (usually a function call)
returning a temporary T
or the T
subobject of a temporary (e.g. a
struct containing T
).The standard-ese may be a little tricky to unpack, so lets discuss some of the edge cases to clarify:
T&
, it must be const T&
. (It’s a
compilation error.)const absl::string_view&
from string
does not extend the lifetime of the string
. (You also don’t want const
absl::string_view&
in the first place, but that’s a separate issue).T&
from a
temporary of U
when T
is a parent class of U
. Please don’t do that:
it’s even more confusing for readers than the other cases.If the lifetime of the temporary is extended, it will last until the reference
goes out of scope. If the lifetime of the temporary isn’t extended in the above
fashion, the T
being referred to is destroyed at the end of the statement
(when we get to the next ;
).
As per TotW 101 you probably shouldn’t rely on lifetime extension in the explicit case of reference-initialization: it’s not gaining you much/any performance, and it is subtle, fragile, and prone to cause extra work for your reviewers and future maintainers.
There are subtle cases where lifetime extension is happening and is necessary and beneficial (like ranged-for over a temporary container), but again, the extension is only for the result of the temporary expression, not any sub-expressions. For instance, these work:
std::vector<int> GetInts();
for (int i : GetInts()) { } // lifetime extension on the vector is important
// Return string_views of size 1 for each char in this string.
std::vector<absl::string_view> Explode(const string& s);
// Lifetime extension kicks in on the vector, but *not* on the temporary string!
for (absl::string_view s : Explode(StrCat("oo", "ps"))) { } // WRONG
This does not work:
MyProto GetProto();
// Lifetime extension *doesn't work* here: sub_protos (a repeated field)
// is destroyed by MyProto going out of scope, and the lifetime extension rules
// don't kick in here to magically lifetime extend the MyProto returned by
// GetProto(). The sub-object lifetime extension only works for simple
// is-a-member-of relationships: the compiler doesn't see that sub_protos()
// itself returning a reference to an sub-object of the outer temporary.
for (const SubProto& p : GetProto().sub_protos()) { } // WRONG