string_viewoperator+ vs. StrCat()absl::Statusstd::bindabsl::optional and std::unique_ptrabsl::StrFormat()make_unique and private Constructors.boolexplicit= delete)switch Statements Responsibly= deleteAbslHashValue 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 #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 Ts. This also applies to accessors that return a
const std::vector<T*>&. You can’t prevent the caller from modifying the Ts.
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.