Pergunta

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?

Foi útil?

Solução

  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.

Outras dicas

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.

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.

Licenciado em: CC-BY-SA com atribuição
scroll top