Search

9.7 — An introduction to std::string_view

In the previous lesson, we talked about C-style strings, and the dangers of using them. C-style strings are fast, but they’re not as easy to use and as safe as std::string.

But std::string (which we covered in lesson 4.12 -- An introduction to std::string), has some of its own downsides, particularly when it comes to const strings.

Consider the following example:

As expected, this prints

hello hello hello

Internally, main copies the string “hello” 3 times, resulting in 4 copies. First, there is the string literal “hello”, which is known at compile-time and stored in the binary. One copy is created when we create the char[]. The following two std::string objects create one copy of the string each. Because std::string is designed to be modifiable, each std::string must contain its own copy of the string, so that a given std::string can be modified without affecting any other std::string object.

This holds true for const std::string, even though they can’t be modified.

Introducing std::string_view

Consider a window in your house, looking at a car sitting on the street. You can look through the window and see the car, but you can’t touch or move the car. Your window just provides a view to the car, which is a completely separate object.

C++17 introduces another way of using strings, std::string_view, which lives in the <string_view> header.

Unlike std::string, which keeps its own copy of the string, std::string_view provides a view of a string that is defined elsewhere.

We can re-write the above code to use std::string_view by replacing every std::string with std::string_view.

The output is the same, but no more copies of the string “hello” are created. The string “hello” is stored in the binary and is not allocated at run-time. text is only a view onto the string “hello”, so no copy has to be created. When we copy a std::string_view, the new std::string_view observes the same string as the copied-from std::string_view is observing. This means that neither str nor more create any copies. They are views onto the existing string “hello”.

std::string_view is not only fast, but has many of the functions that we know from std::string.

Because std::string_view doesn’t create a copy of the string, if we change the viewed string, the changes are reflected in the std::string_view.

We modified arr, but str appears to be changing as well. That’s because arr and str share their string. When you use a std::string_view, it’s best to avoid modifications to the underlying string for the remainder of the std::string_view‘s life to prevent confusion and errors.

Best practice

Use std::string_view instead of C-style strings.

Prefer std::string_view over std::string for read-only strings, unless you already have a std::string.

View modification functions

Back to our window analogy, consider a window with curtains. We can close either the left or right curtain to reduce what we can see. We don’t change what’s outside, we just reduce the visible area.

Similarly, std::string_view contains functions that let us manipulate the view of the string. This allows us to change the view without modifying the viewed string.

The functions for this are remove_prefix, which removes characters from the left side of the view, and remove_suffix, which removes characters from the right side of the view.

This program produces the following output:

Peach
each
ea

Unlike real curtains, a std::string_view cannot be opened back up. Once you change the visible area, you can’t go back (There are tricks which we won’t go into).

std::string_view works with non-null-terminated strings

Unlike C-style strings and std::string, std::string_view doesn’t use null terminators to mark the end of the string. Rather, it knows where the string ends because it keeps track of its length.

This program prints:

aeiou

Ownership issues

Being only a view, a std::string_view‘s lifetime is independent of that of the string it is viewing. If the viewed string goes out of scope, std::string_view has nothing to observe and accessing it causes undefined behavior. The string that a std::string_view is viewing has to have been created somewhere else. It might be a string literal that lives as long as the program does or it was created by a std::string, in which case the string lives until the std::string decides to destroy it or the std::string dies. std::string_view can’t create any strings on its own, because it’s just a view.

What's your name?
nascardriver
Hello nascardriver
Your name is �[email protected][email protected]

When we created str and filled it with std::cin, it created its internal string in dynamic memory. When str goes out of scope at the end of askForName, the internal string dies along with str. The std::string_view doesn’t know that the string no longer exists and allows us to access it. Accessing the released string through view in main causes undefined behavior, which on the author’s machine produced weird characters.

The same can happen when we create a std::string_view from a std::string and modify the std::string. Modifying a std::string can cause its internal string to die and be replaced with a new one in a different place. The std::string_view will still look at where the old string was, but it’s not there anymore.

Warning

Make sure that the underlying string viewed with a std::string_view does not go out of scope and isn’t modified while using the std::string_view.

Converting a std::string_view to a std::string

An std::string_view will not implicitly convert to a std::string, but can be explicitly converted:

This prints:

ball
ball

Converting a std::string_view to a C-style string

Some old functions (such as the old strlen function) still expect C-style strings. To convert a std::string_view to a C-style string, we can do so by first converting to a std::string:

This prints:

ball has 4 letter(s)

However, creating a std::string every time we want to pass a std::string_view as a C-style string is expensive, so this should be avoided if possible.

Opening the window (kinda) via the data() function

The string being viewed by a std::string_view can be accessed by using the data() function, which returns a C-style string. This provides fast access to the string being viewed (as a C-string). But it should also only be used if the std::string_view‘s view hasn’t been modified (e.g. by remove_prefix or remove_suffix) and the string being viewed is null-terminated.

In the following example, std::strlen doesn’t know what a std::string_view is, so we need to pass it str.data():

balloon
7

When a std::string_view has been modified, data() doesn’t always do what we’d like it to. The following example demonstrates what happens when we access data() after modifying the view:

all has 6 letter(s)
str.data() is alloon
str is all

Clearly this isn’t what we’d intended, and is a consequence of trying to access the data() of a std::string_view that has been modified. The length information about the string is lost when we access data(). std::strlen and std::cout keep reading characters from the underlying string until they find the null-terminator, which is at the end of “balloon”.

Warning

Only use std::string_view::data() if the std::string_view‘s view hasn’t been modified and the string being viewed is null-terminated. Using std::string_view::data() of a non-null-terminated string can cause undefined behavior.

Incomplete implementation

Being a relatively recent feature, std::string_view isn’t implemented as well as it could be.

There’s no reason why line 5 and 6 shouldn’t work. They will probably be supported in a future C++ version.


9.8 -- Introduction to pointers
Index
9.6 -- C-style strings

100 comments to 9.7 — An introduction to std::string_view

  • eloooko

    i cant compile. it shows me this error:
    - namespace "std" has no member "string_view"
    i already set on ++17Standard and latest as well.
    can you help me?

  • amateur

    /code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <cstring>
    #include <iostream>
    #include <string_view>

    int main()
    {
      std::string_view str{ "balloon" };

      // Remove the "b"
      str.remove_prefix(1);
      // remove the "oon"
      str.remove_suffix(3);
      // Remember that the above doesn't modify the string, it only changes
      // the region that str is observing.

      std::cout << str << " has " << std::strlen(str.data()) << " letter(s)\n";
      std::cout << "str.data() is " << str.data() << '\n';
      std::cout << "str is " << str << '\n';

      return 0;
    }

    btw what are he function and the meaning of str.data()?
    thanks :)

  • EternalSkid

    I know pretty minor, but under Ownership issues, should line 11 use std::getline(std::cin, str) instead of std::cin ? Please correct me if i'm wrong.

    Also, from how you phrased it, is it true that print can only print out std::string variables, as you mentioned that std::string_view does not implicitly convert, and will cause an compile error. Thanks alex and nascardriver!

    • nascardriver

      It depends on if you want to allow spaces in the name or not. The code in the lesson doesn't allow spaces in the name.
      We defined `print()` to have a `std::string` parameter, that's all it can print.

  • abcdaf

    Hi, i didnt understand why we used the auto keyword here :

  • abcdaf

    Hi, i didnt understand why we used the auto keyword here:

  • chai

    hi, should std::string_view be favoured over const std::string& ??
    and also as mentioned above: However, creating a std::string every time we want to pass a std::string_view as a C-style string is expensive, so this should be avoided if possible.
    Is this true when casting string_view to string?

    Thanks.

    • nascardriver

      > should std::string_view be favoured over const std::string&
      If you know that the caller has a `std::string`, passing by `const std::string&` can be faster. Most of the time, you can't make that assumption, so `std::string_view` should be favored.

      > Is this true when casting string_view to string?
      A cast is no different than creating an unnamed temporary `std::string`.

  • Waldo Lemmer

    Another great lesson :)

    A small correction:

    Section "View modification functions", line 10 of the snippet:
    >   // Ignore the first characters.
    "characters" should be "character"

  • Math

    Hello, I was wondering where this syntax came from :/

  • nyymianon1

    why is the call syntax std::string_view.data() but in the warning box the scope resolution operator std::string_view::data() is used? Or is it covered later somewhere?

    • nascardriver

      `::` is used to refer to members of a namespace or type.
      `.` is used to access members of an instantiated type (A variable).

      • Waldo Lemmer

        I didn't know about this, I think you should mention it in [2.8 — Naming collisions and an introduction to namespaces](learncpp.com/cpp-tutorial/2-9-naming-collisions-and-an-introduction-to-namespaces/) or [6.2 — User-defined namespaces](learncpp.com/cpp-tutorial/user-defined-namespaces/)

  • Nik

    Hello, just a short question regarding one of the statements.
    Statement given above in tutorial:
    "Unlike C-style strings and std::string, std::string_view doesn’t use null terminators to mark the end of the string. Rather, it knows where the string ends because it keeps track of its length."

    For C-style string I agree, but from what I know, there is no null terminator in std::string and in this way std::string and std::string_view are the same.
    Below example is extended example from your tutorial to prove my point
        
    #include <iostream>
    #include <iterator> // For std::size
    #include <string_view>
    #include <string>

    int main()
    {
      // No null-terminator.
      char vowels[]{ 'a', 'e', 'i', 'o', 'u' };

      // vowels isn't null-terminated. We need to pass the length manually.
      // Because vowels is an array, we can use std::size to get its length.
      std::string_view strView{ vowels, std::size(vowels) };
      std::string str{ vowels, std::size(vowels) };

      std::cout << strView << " lenght: " << strView.length() << '\n'; // This is safe. std::cout knows how to print std::string_views.
      std::cout << str << " lenght: " << str.length() << '\n';

      return 0;
    }

    What I understood from your statement is that, unlike std::string, std::string_view doesn’t use null terminators to mark the end of the string because it tracks the length, but std::string also does the same as can be seen in the above example.

    Sorry if I misunderstood something.
    Thanks in advance for the help

    • nascardriver

      `std::string` owns the string. Whatever you initialize a `std::string` with, the `std::string` copies it. When you initialize a `std::string` with a non-null terminated char array, the `std::string` copies it.

      There is no way to access a `std::string` such that it does not appear like a null-terminated string. While it is possible (At least I can't find a contradiction) for a standard library implementation not to use a null-terminated string internally, doing so wouldn't make sense.

      When you initialize the `std::string` with a non-null terminated char array, the `std::string` appends the null-terminator to its internal string. Additionally `std::string` has to track the length.

  • rcetin

    Hello,

    Thank you for this great tutorial. There is a statement:

    "Modifying a std::string can cause its internal string to die and be replaced with a new one in a different place"

    output:
    adasdasdas
    adas

    Here, the address of the std::string is not changed. The new std::string is allocated at the same place in memory. This conflicts with your statement. Does your statement depend on compiler?

    Regards,

    • nascardriver

      Your assignment to `t` may or may not cause a reallocation. The new allocation may or may not happen at the same address the old string was placed it. Accessing `v` after the assignment to `t` causes undefined behavior.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]