【问题标题】:What drawbacks would exist if std::string::substr returned std::string_view?如果 std::string::substr 返回 std::string_view 会有什么缺点?
【发布时间】:2018-02-12 14:17:50
【问题描述】:

看这个例子(取自here):

class foo {
    std::string my_str_;

public:
    std::string_view get_str() const {
        return my_str_.substr(1u);
    }
};

这段代码不好,因为substr返回一个临时的std::string,所以返回的std::string_view指的是一个已经被销毁的对象。但是,如果substr返回std::string_view,就不会存在这个问题了。

此外,如果substr 返回std::string_view 而不是std::string,对我来说似乎是合乎逻辑的,因为返回的字符串是字符串的视图,而且性能更高,因为没有复制。

如果substr 返回std::string_view 是否会有任何缺点(除了明显的缺点:失去与 C++14 的一些兼容性 - 我并没有低估这一点的重要性,我只是想知道是否还有其他缺点存在)?

相关问题:How to efficiently get a `string_view` for a substring of `std::string`

【问题讨论】:

  • string_view 是一个相对较新的东西,标准必须保持向后兼容性。
  • std::string_view sv = my_str_; return sv.substr(1u); 帮忙吗?
  • @DevNull:是的,它解决了问题。但是我想知道如果substr 返回std::string_view 的缺点(除了明显提到的缺点)。在某些情况下,“转换”为std::string_view 可能会自动发生(例如:std::string::substr 可以返回它)。
  • 对于期望临时字符串的人来说,您会遇到相反的问题,可能会将其传递给 C 函数 f(my_str.substr(1,5).c_str());
  • 为什么要坚持改变现有的方法签名而不是引入一个新的,例如substringview 让大家开心?

标签: c++ string c++17


【解决方案1】:

string_view 被发明时,关于它是否应该存在的争论太多了。所有反对的论点都来自你展示的例子。

然而,就像我总是用这样的坏例子告诉大家:C++ 不是 Java,也不是 Python。 C++ 是一种低级语言,您几乎可以完全控制内存,我重复蜘蛛侠的陈词滥调:能力越大,责任越大。如果你不知道string_view 是什么,那就不要使用它!

你问题的另一部分有一个简单的答案,你自己回答了:

如果 substr 返回 std::string_view 会有什么缺点(除了明显的缺点:失去与 C++14 的一些兼容性)?

危害在于每个使用来自substr 的字符串副本的程序可能不再有效。向后兼容在计算机业务中是一件严肃的事情,这就是为什么英特尔的 64 位处理器仍然接受 x86 指令的原因,这也是它们没有倒闭的原因。重新发明轮子要花很多钱,而钱是编程的主要部分。因此,除非您打算将所有 C++ 都扔进垃圾箱并重新开始(就像 RUST 那样),否则您应该在每个新版本中保留旧规则。

您可以弃用某些东西,但要非常小心且非常缓慢。但弃用不像您建议的那样更改 API。

【讨论】:

  • 感谢您的回答!我对我的问题进行了一些编辑,以使自己更清楚地了解缺点部分。
  • 最初的 boost string_view 禁止从 string&& 到 string_view 的转换,因为作者正确地预见到了允许它的危险。自 c++11 以来,c++ 在默认情况下在提高代码正确性方面取得了长足的进步。委员会的这一决定将取消所有这些,并再次将微妙的段错误引入程序中。强大的力量也应该有一个安全装置,所以你不能毫无意义地使用力量。
  • @RichardHodges 这是不想要string_view 的人的反对意见。虽然我完全支持安全,但除了string_view,我没有看到任何其他解决方案。要么是讨厌的字符数组,要么是复制子字符串,要么是string_view
  • @RichardHodges: 是的,但是你不能调用带有临时参数的std::string_view 参数化函数,比如std::string get_name(); fn(get_name());,这也不好。
  • @geza 好吧,这不是灵丹妙药:)
【解决方案2】:

下面是一个具体的(如果稍微不完整的)代码示例,它当前是安全的,但会随着更改而变成未定义的行为:

std::string some_fn();
auto my_substr = some_fn().substr(3, 4);
// ... make use of my_substr ...

可以说,auto 的使用在这里有点可疑,但在以下情况下它是完全合理的(在我看来),重复类型名称几乎是多余的:

const char* some_fn();
auto my_substr = std::string(some_fn()).substr(3, 4);
// ... make use of my_substr ...

编辑:即使substr()总是返回std::string_view,你可以想象这段代码会造成一些痛苦,即使只是在开发/调试期间。

【讨论】:

  • 谢谢,这是一个真正的缺点!
  • 我不确定,为什么第二个版本会导致未定义的行为?因为可能会返回 nullptr 还是什么?
  • @bielu000 从技术上讲,如图所示的 sn-p 是可以的,只有当您稍后使用 my_substr 时才会出现未定义的行为,但我认为这是隐含的,即假定定义了一个变量为了它。我已经编辑了我的答案以明确这一点。
  • @bielu000 但是如果你仍然想知道为什么它是未定义的行为:我说的是一个假设的世界,.substr 返回一个string_view 而不是string,正如问题所问的那样关于。所以表达式std::string(some_fn()) 创建了一个std::string 对象,.substr(3, 4) 返回一个指向该字符串对象的std::string_view。但是一旦语句完成,string 对象就会被销毁并释放它的内存,而string_view 仍然指向它。这意味着以后对my_substr 的任何使用都将访问已被释放的内存。
【解决方案3】:

缺点很明显:这将是一个重大的 API 突破性变化,而 C++ 的每个版本都可以追溯到开始。

C++ 不是一种倾向于破坏 API 兼容性的语言。

【讨论】:

  • 重新设计危险的闹剧,即 string_view/string 关系可能不是一个糟糕的 API 破坏。在没有显式转换的情况下,不可能从临时字符串创建可复制的数据引用对象,例如 string_view。
  • @RichardHodges:破坏string_view 的API 不会像破坏string::substr() 的API 那样具有破坏性。
  • 我们同意这一点。委员会需要解决当前围绕 std::string_view 的许可构造函数集的严重缺陷。
  • 这是正确答案。但是只需要在措辞上狡辩,你的意思是 C++ 标准库不会破坏兼容性 - 不是语言本身?虽然这也是单独的。
【解决方案4】:

一方面,c++ 字符串的底层数据结构与 c 字符串保持大部分兼容(可通过c_str() 成员访问)。 C 字符串以null 终止。所以你基本上只有一个起始 char 指针,并保持递增直到指针指向 0

因此,子字符串可以从原始字符串的任意位置开始。但是,由于您不能只在原始字符串的某处插入 null,因此您的子字符串仍需要在与原始字符串相同的位置结束。

--编辑-- 正如 John Zwinck 所指出的,c++ 字符串可以包含 \0 字符,但这仍然意味着子字符串会丢失它们的 c_str 成员,因为它需要修改原始字符串。 string_view 的一个缺点在 Using std::string_view with api, what expects null terminated string 中也注意到了

【讨论】:

  • C++ std::string 可以很容易地包含空字节,而不仅仅是在末尾。它强调不是 C 字符串,因为它有一个单独的长度字段。
  • @JohnZwinck 确实,std::string 类型通常可以包含嵌入的空字节,但是由于周围的应用程序逻辑,可以保证特定的 std::string 变量不包含空字节.在这种情况下,fn_taking_c_str(my_var.substr(0, 3).c_str()) 是当前可以工作的代码,如果将substr() 更改为返回string_view,它将停止工作。 (尽管至少这会是一个编译错误,并且可以通过更改为fn_taking_c_str(std::string(my_var.substr(0, 3)).c_str()) 轻松纠正。)
猜你喜欢
  • 1970-01-01
  • 2020-03-24
  • 1970-01-01
  • 2017-11-22
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
相关资源
最近更新 更多