std::string_view
已经进入C++17,并且广泛推荐使用它来代替 const std::string&
。
原因之一是性能。
有人可以解释一下当作为参数类型时,std::string_view
与const std::string&
相比究竟如何更快?(假设在被调用函数中没有进行复制)
std::string_view
已经进入C++17,并且广泛推荐使用它来代替 const std::string&
。
原因之一是性能。
有人可以解释一下当作为参数类型时,std::string_view
与const std::string&
相比究竟如何更快?(假设在被调用函数中没有进行复制)
std::string_view
在少数情况下更快。
首先,std::string const&
要求数据在 std::string
中而不是原始的 C 数组、由 C API 返回的 char const*
、一些反序列化引擎生成的 std::vector<char>
等等。避免了格式转换就避免了字节复制,并且(如果字符串长于特定 std::string
实现的 SBO¹)避免了内存分配。
void foo( std::string_view bob ) {
std::cout << bob << "\n";
}
int main(int argc, char const*const* argv) {
foo( "This is a string long enough to avoid the std::string SBO" );
if (argc > 1)
foo( argv[1] );
}
如果 foo
接受的不是 string_view
而是 std::string const&
,那么在 string_view
情况下就没有分配内存。
第二个非常重要的原因是,它允许在不复制的情况下处理子字符串。假设您正在解析一个 2GB 的 JSON 字符串!² 如果将其解析为 std::string
,则每个此类解析节点,在其中存储节点的名称或值时,都会将原始数据从 2GB 的字符串复制到本地节点。
相反,如果将其解析为 std::string_view
,则这些节点将引用原始数据。这可以节省数百万次内存分配并减少解析期间的内存需求一半。
你可以获得的加速效果就是简直令人难以置信。
这是一个极端案例,但其他“获取子字符串并处理它”的情况也可以使用 string_view
产生相当大的速度提升。
做出决策的一个重要部分是使用 std::string_view
所失去的东西。虽然不多,但确实存在。
你失去了隐式的 null 终止符,就这样而已。因此,如果将相同的字符串传递给需要 null 终止符的 3 个函数,可能明智的做法是先转换为 std::string
。 因此,如果您的代码需要 null 终止符,并且不希望从类似 C 样式缓冲区中提供的字符串,则可以采用 std::string const&
。否则使用 std::string_view
。
如果 std::string_view
具有指示其是否以 null 结尾(或更高级别)的标志,则甚至可以消除使用 std::string const&
的最后一个原因。
有一种情况,在这种情况下,采用没有 const&
的 std::string
对于 std::string_view
是最佳的选择。 如果需要在调用后无限期拥有字符串的副本,则按值获取是有效的。 您将处于 SBO 案例中(没有分配,只需复制几个字符即可复制它),或者您将能够将堆分配的缓冲区 “移动” 到本地的 std::string
中。 有两个重载 std::string&&
和 std::string_view
可能更快,但只是轻微的,并且会导致适度的代码膨胀(这可能会导致您失去所有速度增益)。
¹ 小缓冲区优化
² 实际用例。
char const*const* argv
只是展示了 C++ 代码中愚蠢和歧义可以达到的程度... - digito_evostring_view 能够提高性能的一种方式是可以轻松地删除前缀和后缀。在底层,string_view 只需将前缀大小添加到某个字符串缓冲区的指针中,或从字节计数器中减去后缀大小,这通常很快。另一方面,std::string 在进行 substr 等操作时需要复制其字节(这样您就可以获得一个拥有自己的缓冲区的新字符串,但在许多情况下,您只想获取原始字符串的一部分而不进行复制)。例如:
std::string str{"foobar"};
auto bar = str.substr(3);
assert(bar == "bar");
使用std::string_view:
std::string str{"foobar"};
std::string_view bar{str.c_str(), str.size()};
bar.remove_prefix(3);
assert(bar == "bar");
我编写了一个非常简单的基准测试以添加一些真实的数字。我使用了很棒的Google 基准库。被测试的函数包括:
string remove_prefix(const string &str) {
return str.substr(3);
}
string_view remove_prefix(string_view str) {
str.remove_prefix(3);
return str;
}
static void BM_remove_prefix_string(benchmark::State& state) {
std::string example{"asfaghdfgsghasfasg3423rfgasdg"};
while (state.KeepRunning()) {
auto res = remove_prefix(example);
// auto res = remove_prefix(string_view(example)); for string_view
if (res != "aghdfgsghasfasg3423rfgasdg") {
throw std::runtime_error("bad op");
}
}
}
// BM_remove_prefix_string_view is similar, I skipped it to keep the post short
(x86_64 linux,gcc 6.2,"-O3 -DNDEBUG
"):
Benchmark Time CPU Iterations
-------------------------------------------------------------------
BM_remove_prefix_string 90 ns 90 ns 7740626
BM_remove_prefix_string_view 6 ns 6 ns 120468514
string_view
是现有缓冲区中的一个切片,它不需要进行内存分配。string_view
是通过值传递,而非引用传递。char const*
或char[]
一起使用,而无需分配新缓冲区。std::string const&
参数时,没有保证引用字符串不会被修改。因此,在调用不透明方法(指向数据的指针、长度等)后,编译器必须重新获取字符串的内容。string_view
时,编译器可以静态地确定现在在堆栈(或寄存器)上的长度和数据指针不会被其他代码修改。因此,它可以在函数调用之间“缓存”它们。它能够做的一件事情是,在从一个空终止的字符串进行隐式转换的情况下,避免构造一个std::string
对象:
void foo(const std::string& s);
...
foo("hello, world!"); // std::string object created, possible dynamic allocation.
char msg[] = "good morning!";
foo(msg); // std::string object created, possible dynamic allocation.
const std::string str{"goodbye!"}; foo(str);
用 string_view
和用 string&
相比,可能并没有更快的速度。 - Martin Bonner supports Monicastring_view
需要复制两个指针,而 const string&
只需要复制一个指针,那么它不会变得更慢吗? - balkisize_t
)可能会稍微慢一点,但是换取的是使用string_view
更快;要找到std::string
的第一个字符,它需要跟随引用到某个非固定、可能非本地的位置,然后跟随指针到字符串开头找到它。而对于string_view
,它可以直接跟随指针到视图的开头。为了在前面复制两个指针,您可以从每个使用中删除一级间接性。 - ShadowRangerstd::string_view
基本上只是 const char*
的一个包装器。而传递 const char*
意味着与传递 const string&
(或 const string*
)相比,系统中将少一个指针,这意味着:
std::string_view
基本上就像是围绕着一个const char*
的外壳。传递const char*
表示,在与传递const string&
(或const string*
)相比时,系统中将少一个指针,意味着:
string* -> char* -> char[]
( string )
很明显,为了传递const参数,第一个指针是多余的。
p.s. 然而,std::string_view
和const char*
之间一个重要的区别是,string_views不需要以null结尾(它们有内置大小),这允许更长字符串的随机原地接合。
std::string_view
只不过是比较时髦的const char*
而已,就这样。GCC实现它的方式是:class basic_string_view {const _CharT* _M_str; size_t _M_len;}
。 - n.cailloustd::string const*
。而且那个图解很难理解。@n.caillou: 你自己的评论已经比答案更准确了。这使得 string_view
不仅仅是一个“花哨的 char const*
”,这是非常明显的。 - sehestd::string const*
和std::string const&
是相同的吗?请注意,这里只需要翻译,不需要解释或添加其他内容。 - n.caillouconst
引用具有执行临时变量隐式保持活动状态职责(指针无法做到)并防止(非病态的)传递 nullptr 的能力,这是为什么没有人使用 std::string const*
的主要原因之一。 - ShadowRanger这里没有任何答案。问题除了已经构造好的字符串通过const引用和字符串视图被传递之外,没有提供任何其他信息。我认为我们不应该讨论其他情况。在这种特定情况下,我并没有看到任何区别,因为即使是问问题的人明确指定(假设调用方没有进行拷贝),讨论前缀等操作都不是问题的焦点...
也许我漏掉了什么,但就算有区别,差距也非常微小。
std::string_view
只是(char* begin, char* end)
这一对的抽象表示。使用它可以避免不必要的复制,通常在创建std::string
会产生副本时使用。 - QuestionCstd::string
时使用的(string_view 可以接受原始数组、向量、带有非默认分配器的std::basic_string<>
等等等等。哦,当然还有其他的 string_view)。 - sehe