【问题标题】:Why does `std::string::find()` not return the end iterator on failures?为什么 `std::string::find()` 在失败时不返回结束迭代器?
【发布时间】:2020-02-14 05:41:17
【问题描述】:

我发现std::string::find 的行为与标准 C++ 容器不一致。

例如

std::map<int, int> myMap = {{1, 2}};
auto it = myMap.find(10);  // it == myMap.end()

但是对于一个字符串,

std::string myStr = "hello";
auto it = myStr.find('!');  // it == std::string::npos

为什么失败的myStr.find('!') 不应该返回myStr.end() 而不是std::string::npos

由于std::string 与其他容器相比有些特殊,我想知道这背后是否有一些真正的原因。 (令人惊讶的是,我在任何地方都找不到任何人对此提出质疑)。

【问题讨论】:

  • 我认为只有合理的答案才接近于回答以下问题:“为什么热狗是 4 个包装,热狗面包是 6 个包装?”嗯,这就是世界的本来面目
  • 检查this
  • 恕我直言,这种行为的一个原因是std::string 内部由廉价元素的字符组成(就内存而言)。此外,字符是std::string 可以包含的唯一类型。另一方面,std::map 包含更复杂的元素。此外,std::map::find 的规范说它应该找到一个元素,std::string::find 的规范说它的任务是找到位置。
  • 对于 map,不能有 npos 迭代器,所以使用 end 迭代器。对于字符串,我们可以使用 npos,为什么不呢:)

标签: c++ stdstring c++-standard-library


【解决方案1】:

这是因为std::string有两个接口:

  • 在所有容器上都可以找到基于 迭代器 的通用接口
  • std::string 特定的基于 index 的界面

std::string::find 是基于 index 的接口的一部分,因此返回索引。

使用std::find 使用基于通用迭代器的接口。

如果您不想要基于索引的界面,请使用std::vector&lt;char&gt;(不要这样做)。

【讨论】:

    【解决方案2】:

    首先,众所周知,std::string 接口臃肿且不一致,请参阅 Herb Sutter 的 Gotw84 关于此主题的内容。但是,std::string::find 返回一个索引是有原因的:std::string::substr。这个便利的成员函数对索引进行操作,例如

    const std::string src = "abcdefghijk";
    
    std::cout << src.substr(2, 5) << "\n";
    

    您可以实现 substr 以便它接受迭代器到字符串中,但是我们不需要等待很长时间来大声抱怨 std::string 不可用且违反直觉。因此,鉴于std::string::substr 接受索引,您将如何在上述输入字符串中找到第一次出现'd' 的索引,以便打印出从该子字符串开始的所有内容?

    const auto it = src.find('d'); // imagine this returns an iterator
    
    std::cout << src.substr(std::distance(src.cbegin(), it));
    

    这也可能不是您想要的。因此我们可以让std::string::find 返回一个索引,我们在这里:

    const std::string extracted = src.substr(src.find('d'));
    

    如果您想使用迭代器,请使用&lt;algorithm&gt;。它们允许您将上述内容作为

    auto it = std::find(src.cbegin(), src.cend(), 'd');
    
    std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));
    

    【讨论】:

    • 好点。但是,std::string::find 仍然可以返回 size(),而不是 npos,而不是返回迭代器,保持与 substr 的兼容性,同时还避免了几个额外的分支。
    • @erenon 也许,但std::string::substr 已经涵盖了“从这里开始直到结束”的情况,并为第二个索引 (npos) 提供了默认参数。我想返回 size() 也会让人感到困惑,并且像 npos 这样的字面哨兵可能是更好的选择?!
    • @lubgr 但是如果std::string::find 返回一个迭代器,std::string::substr 可能也会接受一个迭代器作为起始位置。在这个替代世界中,您的 find 示例在这两种情况下看起来都是一样的。
    • @MattiasWallin 好点。但是带有迭代器参数的std::string::substr 为进一步的 UB 案例打开了大门(除了索引或迭代器同样可能发生的过去的场景):传递一个引用另一个字符串的迭代器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-13
    • 1970-01-01
    • 2018-11-15
    • 2021-10-27
    • 2022-01-24
    • 1970-01-01
    • 2015-04-29
    相关资源
    最近更新 更多