构造函数使用std::string_view还是std::string和移动语义

3
假设我有一个类,它有一个`std::string`成员,并且我想在其中一个构造函数中获取这个成员的值。
一种方法是使用类型为`std::string`的参数,然后使用`std::move`:
Foo(std::string str) : _str(std::move(str)) {}

据我所了解,移动字符串只会复制其内部指针,这意味着它基本上是免费的,因此传递一个const char*与传递const std::string&一样有效。但是在C++17中,我们有了std::string_view,承诺廉价的拷贝。因此,上述内容可以写成:
Foo(std::string_view str) : _str(str.begin(), str.end()) {}

不需要移动或构建临时的`std::string`,但我认为它实际上只是与以前一样有效地执行相同的操作。
那么我是否遗漏了什么?还是只是在使用带有移动的`std::string`或`std::string_view`之间只是风格问题?

string_view 应该比原始的 char* 稍微快一些,因为它避免了需要调用 strlen 的情况。 - Alan Birtles
2
@AlanBirtles 构造一个从 char*std::string_view 的过程中不会调用 strlen 吗?如果不是,它是如何知道字符串的长度的? - Object object
由于参数是数据池,我认为第一个更合适。当使用rvalue调用时,它将更有效率。 - Eljay
1
是的,我假设你是从std::string开始的(实际上在你的问题中并没有明确说明)。 - Alan Birtles
2个回答

6
不需要移动或构建临时的std::string,但我认为它实际上只是与之前做的事情类似。 这完全取决于用户在调用您的构造函数时具有什么内容。因此,让我们考虑您的第1种情况(std:::string)和第2种情况(std::string_view)。在两种情况下,最终结果都是一个std::string。此外,此分析将忽略小字符串优化。 下面是一些我们可以看到的选项:
  • 用户拥有一个字符串字面值。

    • 在情况1中,将复制到一个std::string参数,然后移动到类中的std::string

    • 在情况2中,将复制指针和大小,然后将字符复制到类中的std::string

    在两种情况下,都必须通过char_traits::length计算字面量的长度。如果用户使用UDL("some_string"s"some_string"sv)在传递参数之前计算参数,则可以避免运行时调用char_traits::length

    因此,在这种情况下,它们基本上是相同的。

  • 用户拥有希望保留值的 std::string lvalue。

    • 在情况1中,将对std::string参数进行复制,然后将其移动到std::string成员中。

    • 在情况2中,将指针和大小复制到std::string_view参数中,然后将字符复制到类中的std::string中。

    在两种情况下,长度都没有被计算,因为std::string知道其长度。同样,在这种情况下,它们是相同的。

  • 用户拥有希望移动到对象中的std::string值。因此,这是一个prvalue或一个显式的std::move

    • 在情况1中,将进行参数的移动构造,然后移动构造成员。

    • 在情况2中,将复制指针和大小,然后复制字符std::string成员中。

    看到区别了吗?在情况1中,字符永远不会被复制;只有移动。这是因为用户拥有的东西和类需要的完全相同。因此,您可以获得最有效的传输。

    在情况2中,必须复制字符,因为string_view参数不知道用户是否想保留字符串。因此,被调用的string成员构造函数也不知道。

当您在转移源类型和目标类型相同时使用中介时,可能会出现低效的情况。如果用户拥有实际上要使用的类型,则从性能角度来看,直接表达该类型更好。如果使用视图中介,则调用方和被调用方之间的信息和意图可能会丢失。
"string_view"是一种通用类型,主要用于当您想使用字符数组而不强制用户使用特定字符串类型时。对于您打算在函数调用之后保留这些字符的用例,通用类型不是最佳选择,因为您唯一可以做的是将它们复制到自己的字符串中。
除非重要的是将 "std:: string"(或您使用的任何字符串类型)排除在接口之外,或者如果用户无法直接传递存储字符的类型(例如,您可能正在存储数组),否则应将其用作参数类型。
但是,除非这个类被大量使用,否则差异微不足道。

3

让我们考虑一些场景:

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
  • 对于情况(A),编译器会创建一个临时字符串,将其传递给Foo的构造函数,并从中移动。
  • 对于情况(B),编译器会复制s1并将其传递给Foo的构造函数,然后从副本中移动。
  • 对于情况(C),编译器会将临时字符串传递给Foo的构造函数,并从中移动。

如果我们有:

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
  • 在情况(A)下,编译器创建一个 string_view(调用 strlen)并传递它。字符数据被复制到 str_ 中。
  • 在情况(B)下,编译器从 s1.data()s1.size() 创建一个 string_view 并传递它。字符数据被复制到 str_ 中。
  • 在情况(C)下,从临时字符串创建一个字符串视图,并传递它。字符数据被复制到 str_ 中。

在所有情况下都没有最佳方法。第一种方法对(A)和(C)很有效,对(B)还可以。第二种方法对(A)和(B)很有效,但对(C)不太有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接