为什么 std::string_view 没有 assign() 和 clear() 方法?

Why doesn't std::string_view have assign() and clear() methods?

这些方法的实现对我来说似乎很简单,它们会使 std::stringstd::string_view 的用法更容易互换。毕竟,std::string_view 具有使对象处于与这些方法相同的状态的构造函数。可以像这样解决缺少的方法:

std::string s {"abcd"};
std::string_view v {s.c_str()};
std::cout << "ctor:   " << v << std::endl; // "abcd"
v = {s.c_str() + 1, 2};
std::cout << "assign: " << v << std::endl; // "bc"
v = {nullptr}; // or even v = {};
std::cout << "clear:  " << v << std::endl; // ""

那么,标准中没有包括这两个显而易见的方法的原因是什么?

更新: 您评论中的一个普遍问题似乎是 "What's the point?",所以我会给您一些背景信息。我正在解析一个大字符串,结果是一个子字符串结构。该结果结构是字符串视图的自然候选者,因此我不必复制所有那些甚至重叠的字符串。结果的一部分是到字符串视图的映射,因此我可能需要在获取键时将它们构造为空,并在获取值后填充它们。解析时我需要跟踪中间字符串,这涉及更新和重置它们。现在它们也可以被字符串视图替换,这就是我遇到那些缺失函数的方式。当然,我可以继续使用字符串或用普通的旧 ptr-ptr 或 ptr-size 对替换它们,但这正是 std::string_view 的用途,对吧?

std::string 接口因其 blown API 而声名狼藉,这就是为什么 std::string_view 不太可能仅仅因为它方便而获得与 std::string 一样多的方法或者使这两种类型更容易互换。

但更重要的是,这些类型并不意味着可以互换。在字符容器上查看 "cleared" 是什么意思?由于 clear() 出现在所有 STL 容器上并做了一些有意义的事情,所以 std::string_view::clear() 会很混乱。

此外,某些数据的视图是供临时使用的,例如只读函数参数。你为什么要分配给它呢?这是一个使用 std::string_view:

的示例函数签名
// Called e.g. with "x86_64-Darwin-16.7.0"
std::string_view extractOSName(std::string_view configStr)
{
    // Parse input, return a new view. Lifetime/ownership managed by caller.
    // No need to re-assign anything, let alone "clearing" them.
}

The implementation of these methods seems straightforward to me and they would make usage of std::string and std::string_view more interchangeable.

std::string_view 无意替代 std::string。它旨在替代 const std::string&assignclear 不是你可以调用的 const std::string& 的成员函数。

这只是猜测,但普遍认为这些操作不太清楚。

我个人认为 "clearing a view" 非常有道理(我们也不要忘记 remove_prefixremove_suffix 存在!虽然见下文...),但我也同意还有其他解释,可能很常见,但意义不大。回想一下,string_view 旨在补充 const std::string&,而不是 std::string,并且您命名的两个函数都不是 std::string 常量接口的一部分。

老实说,我们根本需要这种对话这一事实本身就是一个很好的理由,而不是一开始就没有这个功能。

来自the final proposal for string_view,下面这段话不是关于assignclear具体的,而是作为一个相关观点[lol]进入委员会关于这个主题的想法:

s/remove_prefix/pop_front/, etc.

In Kona 2012, I proposed a range<> class with pop_front, etc. members that adjusted the bounds of the range. Discussion there indicated that committee members were uncomfortable using the same names for lightweight range operations as container operations. Existing practice doesn't agree on a name for this operation, so I've kept the name used by Google's StringPiece.

该提案实际上包括一个 clear(),它被毫不客气地从登记簿中删除 in a later, isolated, rationale-starved proposal

现在,有人可能会争辩说,这些函数因此可以以不同的名称提供,但从未有人提出过,而且很难想象什么替代名称可以解决这个问题,而不是简单地为操作起坏名字。

因为我们可以很容易地分配一个新的string_view,包括一个空的,整个问题都解决了,根本不用费心去解决它。

string_view 对于普通 C 字符串(char 数组)来说 字符串接口包装器 可能很棒,当后备存储不受您的控制时,但是您仍然想要 access/modify 没有能力或需要干预后备存储的内容(即在动态内存分配不成问题的情况下)。

std::string_views 是对内存中其他地方的字符串的引用。但与好的(或坏的)旧 C char * 不同,std::string_view 有一个指针和一个大小。这就是为什么它们可以引用其他字符串的特定部分,这就是为什么 remove_prefix()remove_suffix()string_views 有意义的操作。

但是,您不需要 std::string_viewassign() 方法,因为这与赋值运算符没有什么不同。 std::string 有一个 assign() 方法来处理很多情况,std::string 的赋值运算符无法处理,因为它不能接受多个参数。几个例子:

std::string A { "This is a somewhat lengthy string" };
std::list<char> L { '1', 'A', 'Z' };
std::string B;

B.assign(80, ' ');               // Create an empty line of spaces
B.assign(A, 10, 8);              // Copy out a substring of another string
B.assign(L.begin(), L.end());    // Assign from an STL container

在这三种情况中,只有中间一种是有意义的std::string_view。第一种和最后一种情况都需要分配一个字符串,这是 std::string_view 做不到的。

但是,使用 std::string_view::substr() 可以轻松实现中间情况。例如:

std::string A { "This is a somewhat lengthy string" };
std::string_view V { "Directly pointing to A C string" };
std::string_view B;

B = std::string_view{A}.substr(10, 8);    // Reference substring of A
B = B.substr(0, 4);                       // Further shorten the reference
B = V.substr(0, 8);                       // Reassign with another reference
B = B.substr(0, 0);                       // "Clear" the string_view

因此 clear()assign()std::string_view 都可以使用赋值运算符来完成。 std::string 不是这样:中间赋值 B.assign(A, 10, 8); 用来自 A 的数据覆盖已经分配的 B。使用像 B = A.substr(10, 8); 这样的赋值运算符的解决方案将导致通过 substr() 调用创建临时 std::string,然后内部释放分配给 [=32 的内存=]之前。这通常效率不高,这就是将子字符串分配方法添加到 std::string() 的原因。另一方面,std::string_view::substr() 不需要复制任何字符串数据,因此赋值 B = std::string_view{A}.substr(10, 8); 可以轻松优化!

请注意,尽管以下代码是错误的,并且会在堆上留下对临时子字符串的悬空引用:

std::string A { "This is a somewhat lengthy string" };
std::string_view V { A.substr(10, 8) };

因此,如果您需要将 std::string_view 设置为字符串的子字符串,请始终先转换为 std::string_view(通过 std::string_view(string)),然后执行所需的 [= std::string_view 上的 36=] 方法调用。当然,请确保原始 string 的寿命比新生成的 std::string_view 长,否则最终会出现悬空引用。