Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 5 of 9

Lesson 5.4 established const std::string& as the default for string parameters. There's one case it handles badly, and std::string_view fixes it.

The problem

#include <iostream>
#include <string>

std::size_t length_of(const std::string& text) {
    return text.size();
}

int main() {
    std::string owned = "evil genius";

    std::cout << length_of(owned) << "\n";
    std::cout << length_of("a literal") << "\n";
    return 0;
}
11
9

Both work — but the second call allocates. "a literal" is a const char*, so a temporary std::string is constructed (copying the characters to the heap) just so the reference has something to bind to. Then it's destroyed. All to count characters that were already sitting in the executable.

string_view is a pointer and a length

#include <iostream>
#include <string>
#include <string_view>

std::size_t length_of(std::string_view text) {
    return text.size();
}

int main() {
    std::string owned = "evil genius";

    std::cout << length_of(owned) << "\n";
    std::cout << length_of("a literal") << "\n";
    std::cout << sizeof(std::string_view) << "\n";
    return 0;
}
11
9
16

No allocation, either time. A string_view is just two things — where the characters start and how many there are. 16 bytes, cheap to copy, so it's passed by value, not by reference.

It works with std::string, string literals, and character arrays alike, because all it needs is a pointer and a length.

It's a view, not an owner

The name is the warning. A string_view doesn't own anything — it points into someone else's characters. If they go away, it dangles:

std::string_view view;
{
    std::string temp = "temporary";
    view = temp;             // view points into temp
}                            // temp destroyed here
std::cout << view;           // undefined behaviour — dangling view

And the classic version, which looks completely reasonable:

std::string_view broken() {
    std::string local = "hello";
    return local;            // returns a view of a string that's about to die
}

Same bug as returning a reference to a local (lesson 5.2), just harder to spot because the return type doesn't contain a &.

The rule: use string_view for parameters; don't store one in a class member or return one unless you can prove the underlying characters outlive it.

Cheap slicing

The other real win — taking a substring costs nothing:

#include <iostream>
#include <string>
#include <string_view>

int main() {
    std::string text = "evil genius society";

    std::string copied = text.substr(5, 6);        // allocates
    std::string_view viewed = std::string_view(text).substr(5, 6);   // does not

    std::cout << copied << "\n";
    std::cout << viewed << "\n";

    std::string_view rest = std::string_view(text).substr(12);
    std::cout << rest << " (" << rest.size() << ")\n";
    return 0;
}
genius
genius
society (7)

std::string::substr builds a new string with its own memory. string_view::substr just moves the pointer and adjusts the length. Parsing a large document into fields, that difference is the whole performance story.

The null-terminator caveat

A string_view is not guaranteed null-terminated — it's a pointer and a length, and its end may be the middle of a longer string:

#include <iostream>
#include <string_view>

int main() {
    std::string_view full = "evil genius";
    std::string_view first = full.substr(0, 4);

    std::cout << first << " (" << first.size() << ")\n";
    // first.data() is NOT "evil" — it points at "evil genius"
    return 0;
}
evil (4)

So there's no .c_str(). Passing .data() to a C function that expects a terminated string will read past the end. If you need one, construct a std::string from the view first — std::string(view) — and accept the copy.

Choosing

| Use | When | | --- | --- | | std::string | you own and store the text | | std::string_view | a parameter you only read | | const std::string& | a parameter, when you need .c_str() or an owning API |

For a read-only string parameter, std::string_view is the modern default. It accepts everything const std::string& does, plus literals without allocating.

Try it

Write bool starts_with_word(std::string_view text, std::string_view word) reporting whether text begins with word followed by either a space or the end of the string. Test with "evil genius" against "evil", "evi", and "genius".

Checkpoint

  • std::string_view is a non-owning pointer + length: cheap to copy, pass by value.
  • It avoids the temporary allocation const std::string& causes for string literals.
  • substr on a view is free; on a std::string it allocates.
  • A view does not own its characters — never return one or store one whose source may die.
  • Views are not null-terminated, so there's no .c_str().
  • Default to std::string_view for read-only string parameters.

Questions about this lesson

Log in to ask a question.