Tip of the Week #1: string_view

Originally published as totw/1 on 2012-04-20

By Michael Chastain ([email protected])

Updated 2017-09-18

What’s a string_view, and Why Should You Care?

When creating a function to take a (constant) string as an argument, you have four alternatives: two that you already know, and two of which you might not be aware:

void TakesCharStar(const char* s);             // C convention
void TakesString(const string& s);             // Old Standard C++ convention
void TakesStringView(absl::string_view s);     // Abseil C++ convention
void TakesStringView(std::string_view s);      // C++17 C++ convention

The first two cases work best when a caller has the string in the format already provided, but what happens when a conversion is needed (either from const char* to string or string to const char*)?

Callers needing to convert a string to a const char* need to use the (efficient but inconvenient) c_str() function:

void AlreadyHasString(const string& s) {
  TakesCharStar(s.c_str());               // explicit conversion
}

Callers needing to convert a const char* to a string don’t need to do anything additional (the good news) but will invoke the creation of a (convenient but inefficient) temporary string, copying the contents of that string (the bad news):

void AlreadyHasCharStar(const char* s) {
  TakesString(s); // compiler will make a copy
}

What to Do?

Google’s preferred option for accepting such string parameters is through a string_view. This is a “pre-adopted” type from C++17 - in C++17 builds you should use std::string_view, in any code that can’t rely on C++17 yet you should use absl::string_view.

An instance of the string_view class can be thought of as a “view” into an existing character buffer. Specifically, a string_view consists of only a pointer and a length, identifying a section of character data that is not owned by the string_view and cannot be modified by the view. Consequently, making a copy of a string_view is a shallow operation: no string data is copied.

string_view has implicit conversion constructors from both const char* and const string&, and since string_view doesn’t copy, there is no O(n) memory penalty for making a hidden copy. In the case where a const string& is passed, the constructor runs in O(1) time. In the case where a const char* is passed, the constructor invokes a strlen() automatically (or you can use the two-parameter string_view constructor).

void AlreadyHasString(const string& s) {
  TakesStringView(s); // no explicit conversion; convenient!
}

void AlreadyHasCharStar(const char* s) {
  TakesStringView(s); // no copy; efficient!
}

Because the string_view does not own its data, any strings pointed to by the string_view (just like a const char*) must have a known lifespan, and must outlast the string_view itself. This means that using string_view for storage is often questionable: you need some proof that the underlying data will outlive the string_view.

If your API only needs to reference the string data during a single call, and doesn’t need to modify the data, accepting a string_view is sufficient. If you need to reference the data later or need to modify the data, you can explicitly convert to a C++ string object using string(my_string_view).

Adding string_view into an existing codebase is not always the right answer: changing parameters to pass by string_view can be inefficient if those are then passed to a function requiring a string or a NUL-terminated const char*. It is best to adopt string_view starting at the utility code and working upward, or with complete consistency when starting a new project.

A Few Additional Notes

  • Unlike other string types, you should pass string_view by value just like you would an int or a double because string_view is a small value.
  • string_view is not necessarily NUL-terminated. Thus, it’s not safe to write:
printf("%s\n", sv.data()); // DON’T DO THIS

However, the following is fine:

printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
  • You can output a string_view just like you would a string or a const char*:
std::cout << "Took '" << s << "'";
  • You can convert an existing routine that accepts const string& or NUL-terminated const char* to string_view safely in most cases. The only danger we have encountered in performing this operation is if the address of the function has been taken, this will result in a build break as the resulting function-pointer type will be different.

Subscribe to the Abseil Blog