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 #93 on April 23, 2015
Updated 2023-05-08
Quicklink: abseil.io/tips/93
At Google we are accustomed to using string_view
as function parameters and
return types when we want to deal with unowned strings. It can make the API more
flexible and it can improve performance by avoiding unneeded conversions to
string
. (Tip #1)
string_view
has a more generic cousin called absl::Span
(google3/third_party/absl/types/span.h). Note that though absl::Span
is
similar in utility to std::span
, available in C++ 20, the two types are not
exchangeable.
Span<const T>
is to std::vector<T>
what string_view
is to string
. It
provides a read-only interface to the elements of the vector, but it can also be
constructed from non-vector types (like arrays and initializer lists) without
incurring the cost of copying the elements.
The const
can be dropped, so where Span<const T>
is a view into an array
whose elements can’t be mutated, Span<T>
allows non-const access to the
elements. Unlike spans of const, however, these require explicit construction.
Some of the benefits of using Span
as a function parameter are similar to
those of using string_view
.
The caller can pass a slice of the original vector, or pass a plain array. It is
also compatible with other array-like containers, like absl::InlinedVector
,
absl::FixedArray
, google::protobuf::RepeatedField
, etc.
As with string_view
, it is usually better to pass Span
by value when used as
a function parameter - this form is slightly faster, and produces smaller code.
Example:
void TakesVector(const std::vector<int>& ints); void TakesSpan(absl::Span<const int> ints); void PassOnlyFirst3Elements() { std::vector<int> ints = MakeInts(); // We need to create a temporary vector, and incur an allocation and a copy. TakesVector(std::vector<int>(ints.begin(), ints.begin() + 3)); // No copy or allocations are made when using Span. TakesSpan(absl::Span<const int>(ints.data(), 3)); } void PassALiteral() { // This creates a temporary std::vector<int>. TakesVector({1, 2, 3}); // Span does not need a temporary allocation and copy, so it is faster. TakesSpan({1, 2, 3}); } void IHaveAnArray() { int values[10] = ...; // Once more, a temporary std::vector<int> is created. TakesVector(std::vector<int>(std::begin(values), std::end(values))); // Just pass the array. Span detects the size automatically. // No copy was made. TakesSpan(values); }
A big problem with passing around std::vector<T*>
is that you can’t make the
pointees const without changing the type of the container.
Any function taking a const std::vector<T*>&
will not be able to modify the
vector, but it can modify the T
s. This also applies to accessors that return a
const std::vector<T*>&
. You can’t prevent the caller from modifying the T
s.
Common “solutions” include copying or casting the vector into the right type.
These solutions are slow (for the copy) or undefined behavior (for the cast) and
should be avoided. Instead, use Span
.
Consider these Frob
variants:
void FrobFastWeak(const std::vector<Foo*>& v); void FrobSlowStrong(const std::vector<const Foo*>& v); void FrobFastStrong(absl::Span<const Foo* const> v);
Starting with a const std::vector<Foo*>& v
that needs frobbing, you have two
imperfect options and one good one.
// fast and easy to type but not const-safe FrobFastWeak(v); // slow and noisy, but safe. FrobSlowStrong(std::vector<const Foo*>(v.begin(), v.end())); // fast, safe, and clear! FrobFastStrong(v);
class MyClass { public: // This is supposed to be const. // Don’t modify my Foos, pretty please. const std::vector<Foo*>& shallow_foos() const { return foos_; } // Really deep const. absl::Span<const Foo* const> deep_foos() const { return foos_; } private: std::vector<Foo*> foos_; }; void Caller(const MyClass* my_class) { // Accidental violation of MyClass::shallow_foos() contract. my_class->shallow_foos()[0]->SomeNonConstOp(); // This one doesn't compile. // my_class->deep_foos()[0]->SomeNonConstOp(); }
When used appropriately, absl::Span
can provide decoupling, const correctness
and a performance benefit.
It is important to note that Span
behaves much like string_view
by being a
reference to some externally owned data. All the same warnings apply. In
particular, a Span
must not outlive the data it refers to.