简体   繁体   中英

Constructor taking std::string_view vs std::string and move

Suppose I have a class which has a std::string member, and I want to take the value for this member in one of its constructors.

One approach is to take a parameter of type std::string and then use std::move :

Foo(std::string str) : _str(std::move(str)) {}

As far as I understand it, moving a string just copies its internal pointers meaning its basically free, so passing a const char* will be as efficient as passing a const std::string& .

However in C++17 we got std::string_view , with its promise of cheap copies. So the above could be written as:

Foo(std::string_view str) : _str(str.begin(), str.end()) {}

No need for moves or constructing temporary std::string 's, but I think it actually just does effectively the same thing as before.

So is there something I am missing here? Or is it just a matter of style as to whether you use std::string_view or std::string with move?

No need for moves or constructing temporary std::string's, but I think it actually just does effectively the same thing as before.

That entirely depends on what the user has when they call your constructor. So lets consider your case 1 ( std::string ) and case 2 ( std::string_view ). In both cases, the eventual result is a std::string . Also, this analysis will ignore small string optimization.

So here are some options we can look at:

  • The user has a string literal.

    • in case 1, there will be a copy into a std::string parameter, followed by a move into the std::string in the class.

    • In case 2, there will be a copy of a pointer and size, followed by a copy of the characters into the std::string in the class.

    In both cases, the length of the literal must be computed via char_traits::length at some point. If the user uses a UDL ( "some_string"s or "some_string"sv ) to compute the argument before passing it, then you can avoid the runtime char_traits::length call.

    So in this case, they're basically the same.

  • The user has a std::string lvalue which they want to keep the value of.

    • In case 1, there will be a copy into the std::string parameter, followed by a move into the std::string member.

    • In case 2, there will be a copy of a pointer and size into the std::string_view parameter, followed by a copy of the characters into the std::string in the class

    In both cases, the length is not computed, since std::string knows its length. Again, in this case, they're the same.

  • The user has a std::string value they want to move into the object. So this is either a prvalue or an explicit std::move .

    • In case 1, there will be a move-construction of the parameter, followed by the move-construction of the member.

    • In case 2, there will be a copy of a pointer and size, followed by a copy of the characters into the std::string member.

    See the difference? In case 1, no characters ever get copied; there are only moves. This is because what the user has and what your class needs are identical. So you get the most efficient transfer possible.

    In case 2, characters must get copied, because the string_view parameter has no idea that the user doesn't want to keep the string around. Therefore, neither does the constructor of the string member being invoked.

When you use an intermediary for a transfer where the source and destination types are the same, then you can introduce an inefficiency. If the user has the type you actually intend to use, then it is better performance-wise for your interface to express that type directly. If you use a view intermediary, then information and intent between the caller and the callee can get lost.

string_view is a lingua-franca type; it is primarily intended for when you want to use an array of characters without forcing the user to use a specific string type. For a use case where you intend to keep those characters around beyond the function call, a lingua-franca type is is sub-optimal because the only thing you can do to preserve them is copy them into your own string.

Unless it is important to keep std::string (or whatever string type you use) out of your interface, or if it's impossible for the user to directly pass the type you're storing the characters in (you may be storing an array, for example), you should use it as the parameter type.

But of course, this is all micro-optimization territory. Unless this class is being used a whole bunch, the difference is trivial.

Let's consider some scenarios:

Foo(std::string s) : str_(std::move(s)) {}
string s1;

Foo("abc");           // A - construct from string literal
Foo (s1);             // B - construct from existing string
Foo (string("def"));  // C - construct from temporary string
  • In case (A), the compiler creates a temporary string, passes it to Foo 's constructor, which moves from it.
  • In case (B), the compiler makes a copy of s1 and passes it to Foo 's constructor, which moves from the copy.
  • In case (C), the compiler passes the temporary string to Foo 's constructor, which moves from it.

If instead, we have:

Foo(std::string_view sv) : str_(sv.begin() sv.end()) {}
string s1;

Foo("abc");           // A - construct from string literal
Foo (s1);             // B - construct from existing string
Foo (string("def"));  // C - construct from temporary string
  • In case (A), the compiler creates a string_view (calling strlen ) and passes that. The character data is copied into str_
  • In case (B), the compiler creates a string_view from s1.data() and s1.size() and passes that. The character data is copied into str_
  • In case (C), makes a string view from the temporary string, and passes that. The character data is copied into str_

Neither approach is best in all cases. The first approach works great for (A) and (C), and OK for (B)

The second approach works great for (A), and (B), and not so great for (C).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM