构造函数采用 std::string_view 与 std::string 并移动

Obj*_*ect 5 c++ c++17

假设我有一个包含成员的类std::string,并且我想在其构造函数之一中获取该成员的值。

一种方法是采用类型参数std::string,然后使用std::move

Foo(std::string str) : _str(std::move(str)) {}
Run Code Online (Sandbox Code Playgroud)

据我了解,移动字符串只是复制其内部指针,这意味着它基本上是免费的,因此传递 aconst char*与传递 a 一样有效const std::string&

然而,在 C++17 中,我们得到了std::string_view,并承诺提供廉价副本。所以上面的内容可以写成:

Foo(std::string_view str) : _str(str.begin(), str.end()) {}
Run Code Online (Sandbox Code Playgroud)

不需要移动或构建临时的std::string,但我认为它实际上只是有效地做了与以前相同的事情。

那么我在这里缺少什么吗?std::string_view还是使用或std::string移动只是风格问题?

Nic*_*las 5

不需要移动或构造临时 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他们想要保留其值的左值。

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

    • 在情况 2 中,将有一个指针和大小的副本放入参数中std::string_view,然后将字符的副本放入std::string类中

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

  • 用户std::string想要将值移至对象中。所以这要么是纯右值,要么是显式的std::move

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

    • 在情况 2 中,将有一个指针和大小的副本,然后是放入成员中的字符std::string的副本。

    看到不同?在情况 1 中,不会复制任何字符;只有动作。这是因为用户所拥有的和你的班级所需要的是相同的。这样您就可以获得最有效的传输。

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

当您使用中介进行源类型和目标类型相同的传输时,可能会导致效率低下。如果用户具有您实际打算使用的类型,那么您的界面直接表达该类型在性能方面会更好。如果您使用视图中介,则调用者和被调用者之间的信息和意图可能会丢失。

string_view是通用语类型;它主要用于当您想要使用字符数组而不强制用户使用特定字符串类型时。对于您打算在函数调用之外保留这些字符的用例,通用语言类型不是最佳选择,因为您可以做的唯一保留它们的方法是将它们复制到您自己的字符串中。

除非将(或您使用的任何字符串类型)保留在界面之外很重要std::string,或者用户不可能直接传递您存储字符的类型(例如,您可能存储一个数组),你应该使用它作为参数类型。

当然,这都是微优化领域。除非这个类被大量使用,否则差异是微不足道的。