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
VariablesStatusOr
std::unique_ptr
Must Be MovedAbslStringify()
Originally posted as TotW #93 on April 23, 2015
by Samuel Benzaquen ([email protected])
At Google we are accustomed to using absl::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 std::string
. (See TotW #1.)
absl::string_view
has a more generic cousin called
absl::Span
absl::Span<const T>
is to std::vector<T>
what absl::string_view
is to
std::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 absl::Span<const T>
is a view into an
array whose elements can’t be mutated, absl::Span<T>
allows non-const access
to the elements. Unlike spans of const, however, these require explicit
construction.
std::span
/ gsl::span
It is important to note that, while absl::Span
is similar in design and
purpose to the std::span
proposal (and existing gsl::span
reference
implementation), absl::Span
is not currently guaranteeing to be a drop-in
replacement for any eventual standard, as the std::span
proposal is still
in development and undergoing changes.
Instead, absl::Span
aims to have an interface as similar as possible to
absl::string_view
, without the string-specific functionality.
Some of the benefits of using absl::Span
as a function parameter are similar to
those of using absl::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 absl::string_view
, it is usually better to pass absl::Span
by value
when used as a function parameter; this form is slightly faster than passing by
const reference (on most platforms), and produces smaller code.
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);
}
Because absl::Span
knows its own length, APIs can use it to gain safety over
C-style pointer/length pairs.
memcpy()
// Bad code
void BadUser() {
int src[] = {1, 2, 3};
int dest[2];
memcpy(dest, src, ABSL_ARRAYSIZE(src) * sizeof(int)); // oops! Dest overflowed.
}
// A simple example, but takes advantage that the sizes of the Spans are known
// and prevents the above mistake.
template <typename T>
bool SaferMemCpy(absl::Span<T> dest, absl::Span<const T> src) {
if (src.size() > dest.size()) {
return false;
}
memcpy(dest.data(), src.data(), src.size() * sizeof(T));
return true;
}
void GoodUser() {
int src[] = {1, 2, 3}, dest[2];
// No overflow!
SaferMemCpy(absl::MakeSpan(dest), absl::Span<const int>(src));
}
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
values. This also applies to accessors that
return a const std::vector<T*>&
. You can’t prevent the caller from modifying
the T
values.
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 absl::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);
// Bad code
class DontDoThis {
public:
// Don’t modify my Foos, pretty please.
const std::vector<Foo*>& shallow_foos() const { return foos_; }
private:
std::vector<Foo*> foos_;
};
void Caller(const DontDoThis& my_class) {
// Modifies a foo even though my_class is a reference-to-const
my_class->foos()[0]->SomeNonConstOp();
}
// Good code
class DoThisInstead {
public:
absl::Span<const Foo* const> foos() const { return foos_; }
private:
std::vector<Foo*> foos_;
};
void Caller(const DoThisInstead& my_class) {
// This one doesn't compile.
// my_class.foos()[0]->SomeNonConstOp();
}
When used appropriately, absl::Span
can provide decoupling, const correctness
and a performance benefit.
It is important to note that absl::Span
behaves much like absl::string_view
by being a reference to some externally owned data. All the same warnings apply. In
particular, an absl::Span
must not outlive the data it refers to.