16

I'm using an internal library that was designed to mimic a proposed C++ library, and sometime in the past few years I see its interface changed from using std::string to string_view.

So I dutifully change my code, to conform to the new interface. Unfortunately, what I have to pass in is a std::string parameter, and something that is a std::string return value. So my code changed from something like this:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

to

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

I really don't see what this change bought me as the API client, other than more code (to possibly screw up). The API call is less safe (due to the API no longer owning the storage for its parameters), probably saved my program 0 work (due to move optimizations compilers can do now), and even if it did save work, that would only be a couple of allocations that will not and would never be done after startup or in a big loop somewhere. Not for this API.

However, this approach seems to follow advice I see elsewhere, for example this answer:

As an aside, since C++17 you should avoid passing a const std::string& in favor of a std::string_view:

I find that advice surprising, as it seems to be advocating universally replacing a relatively safe object with a less safe one (basically a glorified pointer and length), primarily for purposes of optimization.

So when should string_view be used, and when should it not?

  • 1
    you should never have to call the std::string_view constructor directly, you should just pass the strings to the method taking a std::string_view directly and it will automatically convert. – Mgetz Jan 16 '18 at 20:29
  • @Mgetz - Hmmm. I'm not (yet) using a full-blown C++17 compiler, so perhaps that's most of the issue. Still, the sample code here seemed to indicate its required, at least when declaring one. – T.E.D. Jan 16 '18 at 20:43
  • 4
    See my answer the conversion operator is in the <string> header and happens automatically. That code is deceiving and wrong. – Mgetz Jan 16 '18 at 20:46
  • 1
    "with a less safe one" how is a slice less safe than a string reference? – CodesInChaos Jan 16 '18 at 20:55
  • 3
    @T.E.D. The caller can just as easily free the string your reference is pointing to as they can free the memory the slice is pointing into. – CodesInChaos Jan 16 '18 at 21:31
18
  1. Does the functionality taking the value need to take ownership of the string? If so use std::string (non-const, non-ref). This option gives you the choice to explicitly move in a value as well if you know that it won't ever be used again in the calling context.
  2. Does the functionality just read the string? If so use std::string_view (const, non-ref) this is because string_view can handle std::string and char* easily without issue and without making a copy. This should replace all const std::string& parameters.

Ultimately you should never need to call the std::string_view constructor like you are. std::string has a conversion operator that handles the conversion automatically.

  • Just to clarify one point, I'm thinking such a conversion operator would also take care of the worst of the lifetime issues, by making sure your RHS string value stays around for the entire length of the call? – T.E.D. Jan 16 '18 at 21:00
  • 3
    @T.E.D. if you're just reading the value then the value will outlast the call. If you're taking ownership then it needs to outlast the call. Hence why I addressed both cases. The conversion operator just deals with making std::string_view easier to use. If a developer mis-uses it in an owning situation that's a programming error. std::string_view is strictly non-owning. – Mgetz Jan 16 '18 at 21:02
  • Why const, non-ref ? The parameter being const is up to the specific use, but in general is reasonable as non-const. And you missed 3. Can accept slices – v.oddou May 13 at 7:14
14

A std::string_view brings some of the benefits of a const char* to C++: unlike std::string, a string_view

  • does not own memory,
  • does not allocate memory,
  • can point into an existing string at some offset, and
  • has one less level of pointer indirection than a std::string&.

This means a string_view can often avoid copies, without having to deal with raw pointers.

In modern code, std::string_view should replace nearly all uses of const std::string& function parameters. This should be a source-compatible change, since std::string declares a conversion operator to std::string_view.

Just because a string view doesn't help in your specific use case where you need to create a string anyway does not mean that it's a bad idea in general. The C++ standard library tends to be optimized for generality rather than for convenience. The “less safe” argument doesn't hold, as it shouldn't be necessary to create the string view yourself.

  • 2
    The big drawback of std::string_view is the absence of a c_str() method, resulting in unnecessary, intermediate std::string objects that need to be constructed and allocated. This is especially a problem in low-level APIs. – Matthias Jun 21 '18 at 17:12
  • 1
    @Matthias That's a good point, but I don't think its a huge drawback. A string view allows you to point into an existing string at some offset. That substring cannot be zero-terminated, you need a copy for that. A string view does not prohibit you from making a copy. It allows many string processing tasks that can be performed with iterators. But you are right that APIs that need a C string won't profit from views. A string reference can then be more appropriate. – amon Jun 21 '18 at 17:27
  • @Matthias, doesn't string_view::data() match c_str()? – Aelian Nov 21 '18 at 20:30
  • 3
    @Jeevaka a C string has to be zero-terminated, but a string view's data is usually not zero-terminated because it points into an existing string. E.g. if we have a string abcdef\0 and a string view that points at the cde substring, there is no zero character after the e – the original string has an f there. The standard also notes: “data() may return a pointer to a buffer that is not null-terminated. Therefore it is typically a mistake to pass data() to a function that takes just a const charT* and expects a null-terminated string.” – amon Nov 21 '18 at 21:04
  • 1
    @kayleeFrye_onDeck The data already is a char pointer. The problem with C strings is not getting a char pointer, but that a C string must be null-terminated. See my previous comment for an example. – amon Feb 20 at 6:40
8

I find that advice surprising, as it seems to be advocating universally replacing a relatively safe object with a less safe one (basically a glorified pointer and length), primarily for purposes of optimization.

I think this is slightly misunderstanding the purpose of this. While it is an "optimization", you should really think of it as unshackling yourself from having to use a std::string.

Users of C++ have created dozens of different string classes. Fixed-length string classes, SSO-optimized classes with the buffer size being a template parameter, string classes that store a hash value used to compare them, etc. Some people even use COW-based strings. If there's one thing C++ programmers love to do, it's write string classes.

And that ignores strings which are created and owned by C libraries. Naked char*s, maybe with a size of some kind.

So if you're writing some library, and you take a const std::string&, the user now has to take whatever string they were using and copy it to a std::string. Maybe dozens of times.

If you want access to std::string's string-specific interface, why should you have to copy the string? That's such a waste.

The principle reasons not to take a string_view as a parameter are:

  1. If your ultimate goal is to pass the string to an interface that takes a NUL-terminated string (fopen, etc). std::string is guaranteed to be NUL terminated; string_view isn't. And it's very easy to substring a view to make it non-NUL-terminated; sub-stringing a std::string will copy the substring out into a NUL-terminated range.

    I wrote a special NUL-terminated string_view style type for exactly this scenario. You can do most operations, but not ones that break its NUL-terminated status (trimming from the end, for example).

  2. Lifetime issues. If you really need to copy that std::string or otherwise have the array of characters outlive the function call, it's best to state this up-front by taking a const std::string &. Or just a std::string as a value parameter. That way, if they already have such a string, you can claim ownership of it immediately, and the caller can move into the string if they don't need to keep a copy of it around.

  • Is this true? The only standard string class I was aware of in C++ prior to this was std::string. There's some support for using char*'s as "strings" for backward compatibility with C, but I almost never need to use that. Sure, there are lots of user-defined third party classes for almost anything you can imagine, and strings are probably included in that, but I almost never have to use those. – T.E.D. Jan 18 '18 at 15:12
  • @T.E.D.: Just because you "almost never have to use those" doesn't mean that other people don't routinely use them. string_view is a lingua franca type that can work with anything. – Nicol Bolas Jan 18 '18 at 15:23
  • 3
    @T.E.D.: That's why I said "C++ as a programming environment", as opposed to "C++ as a language/library." – Nicol Bolas Jan 18 '18 at 15:28
  • 2
    @T.E.D.: "So I could equally say "C++ as a programming environment has thousands of container classes"?" And it does. But I can write algorithms that work with iterators, and any container classes that follow that paradigm will work with them. By contrast, "algorithms" that can take any contiguous array of characters were much harder to write. With string_view, it's easy. – Nicol Bolas Jan 18 '18 at 16:57
  • 1
    @T.E.D.: Character arrays are a very special case. They are exceedingly common, and different containers of contiguous characters differ only in how they manage their memory, not in how you iterate across the data. So having a single lingua franca range type that can cover all such cases without having to employ a template makes sense. Generalization beyond this is the province of the Range TS and templates. – Nicol Bolas Jan 19 '18 at 19:28

protected by gnat Sep 19 '18 at 7:41

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.