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?
3 Answers 3
- 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. - Does the functionality just read the string? If so use
std::string_view
(const, non-ref) this is becausestring_view
can handlestd::string
andchar*
easily without issue and without making a copy. This should replace allconst 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.
-
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– Mgetz2018年01月16日 21:02:16 +00:00Commented Jan 16, 2018 at 21:02 -
1What's the problem of passing
const std::string_view &
in place ofconst std::string &
?ceztko– ceztko2019年12月16日 23:01:47 +00:00Commented Dec 16, 2019 at 23:01 -
3@ceztko you're obviously free to do that, I would generally suggest against it because it obfuscates the lifetime of the
string_view
by creating a temporary. Compilers can easily optimize most of this away, but clear lifetime is at least for me a must since this is a non-owning structure. I want the API to be clear I'm giving this as a borrow only.Mgetz– Mgetz2019年12月17日 13:50:45 +00:00Commented Dec 17, 2019 at 13:50 -
1@DonHatch That "implementation-detail" mirrors a major design-point, and is thus quite stable and non-surprising.Deduplicator– Deduplicator2021年04月11日 13:14:01 +00:00Commented Apr 11, 2021 at 13:14
-
1@DonHatch - That seems to be a fundamental design problem with string_view. Its effectively a pointer to the string object its created from, created as a manual developer optimization. So the developer is forced to be cognizant of how its going to be used.T.E.D.– T.E.D.2022年01月20日 22:14:47 +00:00Commented Jan 20, 2022 at 22: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.
-
6The big drawback of
std::string_view
is the absence of ac_str()
method, resulting in unnecessary, intermediatestd::string
objects that need to be constructed and allocated. This is especially a problem in low-level APIs.Matthias– Matthias2018年06月21日 17:12:55 +00:00Commented Jun 21, 2018 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– amon2018年06月21日 17:27:50 +00:00Commented Jun 21, 2018 at 17:27
-
4@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
abcdef0円
and a string view that points at thecde
substring, there is no zero character after thee
– the original string has anf
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– amon2018年11月21日 21:04:21 +00:00Commented Nov 21, 2018 at 21:04 -
2@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– amon2019年02月20日 06:40:09 +00:00Commented Feb 20, 2019 at 6:40
-
3@amon - I just (finally) got the opportunity to use a full-blown C++17 compiler for writing an API using string_view, and I had to abandon string_view for exactly the reason Matthias mentioned. I actually love the idea of moving away from null-terminated strings in theory, but the fact that its API doesn't support them at all means string_view is worse than useless if the underlying code interfaces that string with null-terminated string routines or std::string. This includes not only old C char array handling routines and OS APIs, but C++ standard library operations like std::*fstream.T.E.D.– T.E.D.2020年02月22日 02:43:55 +00:00Commented Feb 22, 2020 at 2:43
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:
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 astd::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).
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 aconst std::string &
. Or just astd::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.
-
3@T.E.D.: That's why I said "C++ as a programming environment", as opposed to "C++ as a language/library."Nicol Bolas– Nicol Bolas2018年01月18日 15:28:23 +00:00Commented Jan 18, 2018 at 15:28
-
1What does that even mean then? The superset of all code everyone has written anywhere? So I could equally say "C++ as a programming environment has thousands of container classes"?T.E.D.– T.E.D.2018年01月18日 16:26:33 +00:00Commented Jan 18, 2018 at 16:26
-
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– Nicol Bolas2018年01月18日 16:57:02 +00:00Commented Jan 18, 2018 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– Nicol Bolas2018年01月19日 19:28:21 +00:00Commented Jan 19, 2018 at 19:28
-
2Another reason to take a
const std::string &
is when that string will just be used as the key to an associative container, like astd::map<std::string, Foo>
. If you take astd::string_view
instead, you'll have to make a temporary copy before you can use it as a key.Adrian McCarthy– Adrian McCarthy2020年07月08日 18:26:48 +00:00Commented Jul 8, 2020 at 18:26
std::string_view
constructor directly, you should just pass the strings to the method taking astd::string_view
directly and it will automatically convert.<string>
header and happens automatically. That code is deceiving and wrong.