什么时候应该在接口中使用string_view?


16

我正在使用一个内部库,该库旨在模仿拟议的C ++库,并且在过去几年中的某个时候,我看到其接口已从使用更改std::stringstring_view

因此,我忠实地更改了代码,以适应新的界面。不幸的是,我必须传递的是std :: string参数,以及返回的std :: string值。所以我的代码从这样的事情改变了:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

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()));
}

除了更多的代码(可能搞砸了)之外,我真的看不到这种变化给我作为API客户端带来了什么。API调用不太安全(由于API不再拥有其参数的存储空间),可能保存了我的程序0的工作(由于编译器现在可以进行移动优化),即使确实保存了工作,也只能在启动后或在某个地方的大循环中,将不会并且永远不会完成的一些分配。不适用于此API。

但是,这种方法似乎遵循我在其他地方看到的建议,例如,以下答案

顺便说一句,从C ++ 17开始,您应该避免传递const std :: string&,而推荐使用std :: string_view:

我发现该建议令人惊讶,因为它似乎在倡导以安全性较低的对象(基本上是经过修饰的指针和长度)普遍替换为相对安全的对象,主要是出于优化目的。

那么什么时候应该使用 string_view,什么时候不应该使用?


1
您永远不必std::string_view直接调用构造函数,只需将字符串传递给采用std::string_view直接方法的方法,它将自动转换。
Mgetz

@Mgetz-嗯。我(尚未)使用成熟的C ++ 17编译器,所以也许这是最主要的问题。尽管如此,示例代码这里似乎至少在声明一个就表明了其要求。
TED

4
看到我的答案,转换运算符位于<string>标题中,并且自动发生。该代码具有欺骗性和错误性。
Mgetz

1
“安全性较低”切片比字符串引用安全性如何?
CodesInChaos

3
@TED调用者可以像释放引用所指向的内存一样轻松地释放引用所指向的字符串。
CodesInChaos

Answers:


18
  1. 带有值的功能是否需要拥有字符串的所有权?如果是这样,请使用std::string(非常量,非引用)。如果您知道不会再在调用上下文中再次使用该值,则可以通过此选项选择显式移动值。
  2. 该功能是否只读取字符串?如果这样,则使用std::string_view(const,non-ref),这是因为string_view可以轻松地处理std::stringchar*不会出现问题并且无需进行复制。这应该替换所有const std::string&参数。

最终,您永远不需要std::string_view像您一样调用构造函数。std::string有一个转换运算符,可以自动处理转换。


只是为了澄清一点,我认为这样的转换运算符还可以通过确保您的RHS字符串值在整个通话期间都保持不变来解决生命周期中最糟糕的问题?
TED

3
@TED如果您只是读取该值,则该值将比调用持久。如果您要拥有所有权,则它需要使通话时间更长。因此,为什么我要处理这两种情况。转换运算符只是为了使其std::string_view易于使用。如果开发人员在自己的情况下滥用它,那就是编程错误。std::string_view完全是非所有。
Mgetz

为什么const, non-ref呢 参数为const取决于特定用途,但通常作为非const是合理的。而您错过了3。可以接受切片
v.oddou

const std::string_view &代替的问题是什么const std::string &
ceztko

@ceztko完全没有必要,并且在访问数据时添加了额外的间接寻址。
Mgetz

15

A std::string_view带来了a的一些好处const char* C ++:与std::string,string_view 不同

  • 没有自己的记忆,
  • 不分配内存,
  • 可以以一定的偏移量指向现有的字符串,并且
  • 的指针间接寻址级别比少std::string&

这意味着string_view通常可以避免复制,而不必处理原始指针。

在现代代码中,std::string_view应几乎替代所有使用const std::string&功能参数的方法。这应该是与源兼容的更改,因为std::string将转换操作符声明为std::string_view

仅仅因为字符串视图在您仍然需要创建字符串的特定用例中无济于事并不意味着它通常就不是一个好主意。C ++标准库倾向于针对通用性而非便利性进行优化。“不太安全”的参数不成立,因为不需要自己创建字符串视图。


2
最大的缺点std::string_view是缺少 c_str()方法,导致不必要的中间std::string对象需要构造和分配。在低级API中,这尤其是个问题。
Matthias

1
@Matthias这是一个很好的观点,但是我认为它没有很大的缺点。字符串视图允许您以某个偏移量指向现有字符串。该子字符串不能以零结尾,因此您需要一个副本。字符串视图并不禁止您进行复制。它允许使用迭代器执行许多字符串处理任务。但是您说对了,需要C字符串的API不会从视图中受益。字符串引用可能更合适。
阿蒙(Amon)

@ Matthias,string_view :: data()与c_str()不匹配吗?
艾利安

3
@Jeevaka C字符串必须终止于零,但是字符串视图的数据通常不终止于零,因为它指向现有的字符串。例如,如果我们有一个字符串abcdef\0和一个指向cde子字符串的字符串视图,则e-原始字符串后面没有零字符f。该标准还指出:“ data()可能会返回一个指向非空终止缓冲区的指针。因此,将data()传递给仅接受const charT *并期望以空字符结尾的字符串的函数通常是一个错误。”
amon

1
@kayleeFrye_onDeck数据已经是一个char指针。C字符串的问题是没有获得char指针,但是C字符串必须以空值结尾。有关示例,请参见我之前的评论。
阿蒙

8

我发现该建议令人惊讶,因为它似乎在倡导以安全性较低的对象(基本上是经过修饰的指针和长度)普遍替换为相对安全的对象,主要是出于优化目的。

我认为这有点误解了此目的。尽管这是一种“优化”,但您应该真正将其视为不必使用std::string

C ++用户已经创建了许多不同的字符串类。固定长度的字符串类,缓冲区大小作为模板参数的SSO优化类,存储用于比较它们的哈希值的字符串类等。有些人甚至使用基于COW的字符串。如果C ++程序员喜欢做一件事,那就是编写字符串类。

并且忽略由C库创建和拥有的字符串。裸char*s,可能具有某种大小。

因此,如果您正在编写某个库,并且使用a const std::string&,则用户现在必须使用他们使用的任何字符串并将其复制到a std::string。也许几十次。

如果您想访问std::string的字符串专用接口,为什么还要复制字符串?真是浪费。

不将a string_view作为参数的原则原因是:

  1. 如果您的最终目标是将字符串传递到采用NUL终止的字符串(fopen等)的接口。std::string确保被NUL终止;string_view不是。对视图进行子串化以使其成为非NUL终止非常容易;子std::string字符串a 将把子字符串复制到NUL终止的范围内。

    我正是针对这种情况编写了一种特殊的NUL终止的string_view样式类型。您可以执行大多数操作,但不能执行破坏其NUL终止状态的操作(例如,从末尾开始修剪)。

  2. 终身问题。如果您确实需要复制该std::string字符或以其他方式使字符数组超出函数调用的范围,则最好通过使用预先声明const std::string &。或只是std::string作为值参数。这样,如果他们已经有了这样的字符串,则可以立即声明其所有权,如果调用者不需要保留其副本,则调用者可以移入该字符串。


这是真的?在此之前,我在C ++中知道的唯一标准字符串类是std :: string。有一些支持使用char *作为“字符串”以实现与C的向后兼容性,但我几乎不需要使用它。当然,对于您可以想象的任何事物,都有许多用户定义的第三方类,并且其中可能包含字符串,但是我几乎不必使用它们。
TED

@TED:仅仅因为您“几乎永远不必使用那些”,并不意味着其他人不会例行使用它们。string_view是一种通用语言,可以与任何东西一起使用。
Nicol Bolas

3
@TED:这就是为什么我说“ C ++作为编程环境”,而不是“ C ++作为语言/库”的原因。
Nicol Bolas

2
@TED:“ 所以,我同样可以说‘C ++为编程环境有成千上万的集装箱班’?而它”。但是我可以编写与迭代器一起使用的算法,遵循该范例的任何容器类都可以与它们一起使用。相比之下,可以采用任何连续字符数组的“算法”则很难编写。使用string_view,很容易。
Nicol Bolas

1
@TED:字符数组是一个非常特殊的情况。它们极为常见,并且连续字符的不同容器的不同之处仅在于它们管理内存的方式,而不在于您遍历数据的方式。因此,拥有一个可以覆盖所有此类情况而无需使用模板的通用语言范围类型是很有意义的。除此之外,还有范围TS和模板的范围。
Nicol Bolas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.