如何有效地为std :: string的子字符串获取`string_view`


76

使用http://en.cppreference.com/w/cpp/string/basic_string_view作为参考,我认为没有办法更优雅地做到这一点:

std::string s = "hello world!";
std::string_view v = s;
v = v.substr(6, 5); // "world"

更糟糕的是,幼稚的方法是一个陷阱,并留下v了对临时对象的悬挂:

std::string s = "hello world!";
std::string_view v(s.substr(6, 5)); // OOPS!

似乎记得类似的东西,可能在标准库中添加了一个将子字符串作为视图返回的内容:

auto v(s.substr_view(6, 5));

我可以想到以下解决方法:

std::string_view(s).substr(6, 5);
std::string_view(s.data()+6, 5);
// or even "worse":
std::string_view(s).remove_prefix(6).remove_suffix(1);

坦白说,我认为这些都不是很好。现在,我能想到的最好的事情就是使用别名来使事情变得不再那么冗长。

using sv = std::string_view;
sv(s).substr(6, 5);

4
“我认为这些都不是很好。”第一个有什么问题?对我来说似乎完全清楚。编辑:顺便说一句,将两个具有明确含义的方法组合起来(string_view(s).substr(...))似乎比单个函数同时执行两项操作()更好,.substr_view(...)即使它确实存在。
亚瑟塔卡

2
@sehe:您是否建议std::string::substr返回一个std::string_view
Geza

1
@geza我似乎强烈记得std::basic_string<>要添加接口的操作subtr_view,该操作的确添加了,如。我在问题中提到了这一点。我希望有人能回答并说:“那是Nxxxx的提案被拒绝/接受到C ++ zz或
TSn中

1
由于@ArthurTacca的操作如此普遍,我认为应该进行1步操作,可能还会更高效。且容易出错的肯定少:coliru.stacked-crooked.com/a/fd180519dc9b2f00免费的功能现在是我们能做的最好的(中由于缺少en.wikipedia.org/wiki/Uniform_Function_Call_Syntax
sehe

4
@sehe,所以我在cpporg组中的危言耸听的结果是,没有人似乎认为string_view是一个问题。答案似乎是“永远不会返回string_view”,将string_view添加到一个人应该“只是知道”不返回的类的任意列表中。在这种情况下,该std::string_view::substr()方法将打破自己的规则,因为它返回的是string_view。因此,我认为建议永远不要这样做。使用std::string
理查德·霍奇斯

Answers:


46

有自由功能路线,但是除非您也提供过载,std::string否则这是一个蛇坑。

#include <string>
#include <string_view>

std::string_view sub_string(
  std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;

  // this is fine and elegant...
  auto bar = sub_string(source, 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

恕我直言,string_view的整个设计是一个恐怖的表演,它将带我们回到一个充满争议和愤怒的客户的世界。

更新:

甚至添加超载std::string也是恐怖的表演。看看您是否能发现微妙的段错误定时炸弹...

#include <string>
#include <string_view>

std::string_view sub_string(std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string&& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string const& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;
  auto bar = sub_string(std::string_view(source), 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

编译器在这里没有发现任何警告。我敢肯定,代码审查也不会。

我已经说过了,再说一遍,以防c ++委员会上的任何人在看,允许从std::string到的隐式转换std::string_view是一个可怕的错误,只会使c ++蒙羞

更新资料

在cpporg留言板上提出了string_view的这个(对我来说)相当令人震惊的属性后,我的关心变得冷漠。

该小组的建议的共识是,std::string_view绝不能从函数中返回它,这意味着我上面的第一个提议是错误的形式。

当然,如果偶然发生(例如通过模板扩展),则没有编译器帮助来捕获时间。

因此,std::string_view应格外小心,因为从内存管理的角度来看,它等效于指向另一个对象状态的可复制指针,该对象可能不再存在。但是,它在所有其他方面的外观和行为都类似于值类型。

因此,代码如下:

auto s = get_something().get_suffix();

get_suffix()返回std::string(按值或引用)时是安全的

但如果将get_suffix()重构为返回a,则为UB std::string_view

在我的拙劣观点中,这意味着auto如果将他们调用的库重构为std::string_view代替的返回值,则使用返回的字符串存储的任何用户代码都将中断std::string const&

因此,从现在开始,至少对于我来说,“几乎总是自动的”必须变成“几乎总是自动的,除非是字符串”。


3
@sehe的更新情况更糟-善意错误的结果。希望很快就会在您附近的代码库中看到这一点……
Richard Hodges

9
@sehe我感到惊讶的是,委员会没有看到这种情况。他们似乎一度认为现有算法的快速修复性能优于代码安全性。我担心这是一个严重的错误。
理查德·霍奇斯

3
谁说他们没有看到它来?它一定是称重选项。我的意思string foo(); bool bar(string_view); auto check = bar(foo());是,允许允许安全合理。
sehe

6
@sehe当然我了解原理。很吸引人 如果string_view是不可复制和不可移动的,则可以安全地实现。这样一来,您就无法从函数中返回一个,一切都会好起来的。
理查德·霍奇斯

6
@geza您的问题很好地证实了我的关注。的第二个分配bar是从临时构造的string_view构造中构造的,该string_view构造std::string现在已被销毁。
理查德·霍奇斯

14

您可以使用从std :: stringstd :: string_view的转换运算符:

std::string s = "hello world!";
std::string_view v = std::string_view(s).substr(6, 5);

这种方法显然效率不高(即是否在不分配额外的临时缓冲区的情况下完成了工作?),因此支持效率的主张的评论将是不错的。实际上,这种方法似乎是非常禁忌的,因为它v指向立即变为无效的临时缓冲区。
John Doggett

1

这样可以有效地创建子字符串string_view。

#include <string>
inline
std::string_view substr_view(const std::string &s,size_t from,size_t len) {
  if( from>=s.size() ) return {};
  return std::string_view(s.data()+from,std::min(s.size()-from,len));
}

#include <iostream>
int main(void) {
  std::cout << substr_view("abcd",3,11) << "\n";

  std::string s {"0123456789"};
  std::cout << substr_view(s,3,2) << "\n";

  // be cautious about lifetime, as illustrated at https://en.cppreference.com/w/cpp/string/basic_string_view
  std::string_view bad = substr_view("0123456789"s, 3, 2); // "bad" holds a dangling pointer
  std::cout << bad << "\n"; // possible access violation

  return 0;
}

突出显示“不良使用”可能是一个好主意,因为给定的示例很容易演变为一个。提议进行编辑以实现此目的。
John Doggett
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.