38

在 C++11 之后,我想到了c_str()data() .

C++17 为后者引入了一个重载,它返回一个非常量指针(reference,我不确定它是否完全更新了 C++17):

const CharT* data() const;    (1)   
CharT* data();                (2)   (since C++17)

c_str()只返回一个常量指针:

const CharT* c_str() const;

为什么这两种方法在 C++17 中有所不同,尤其是在 C++11 是使它们同质化的时候?换句话说,为什么只有一个方法重载,而另一个没有?

4

4 回答 4

22

open-std.org上的这篇data()论文解释了成员超载的原因。

TL;论文博士:添加了非常量.data()成员函数 forstd::string以提高标准库的一致性并帮助 C++ 开发人员编写正确的代码。在调用对其 C 字符串参数没有 const 限定的 C 库函数时,它也很方便。

论文中的一些相关段落:

摘要缺少非常量成员函数
是疏忽还是基于 C++11 之前的语义的有意设计?在任何一种情况下,这种功能的缺乏都会诱使开发人员在几个合法的场景中使用不安全的替代方案。本文主张为std::string 添加一个非常量成员函数,以提高标准库的一致性并帮助 C++ 开发人员编写正确的代码。std::string.data()std::string.data()

用例
C 库有时包含具有 char * 参数的例程。一个例子是Windows APIlpCommandLine中函数的参数。CreateProcess因为 的data()成员std::string是 const,所以它不能用于使 std::string 对象与 lpCommandLine参数一起工作。开发人员很想.front()改用,如下例所示。

std::string programName;
// ...
if( CreateProcess( NULL, &programName.front(), /* etc. */ ) ) {
  // etc.
} else {
  // handle error
}

请注意,当programName为空时,programName.front()表达式会导致未定义的行为。一个临时的空 C 字符串修复了这个错误。

std::string programName;
// ...

if( !programName.empty() ) { 
  char emptyString[] = {'\0'};    
  if( CreateProcess( NULL, programName.empty() ? emptyString : &programName.front(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

如果有一个非常量.data()成员,就像 with 一样std::vector,正确的代码会很简单。

std::string programName;
// ...
if( !programName.empty() ) {
  char emptyString[] = {'\0'};
  if( CreateProcess( NULL, programName.data(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

.data() std::string在调用对其 C 字符串参数没有 const 限定的 C 库函数时,非 const成员函数也很方便。这在较旧的代码和需要与较旧的 C 编译器一起移植的代码中很常见。

于 2018-11-27T13:12:15.203 回答
22

P0272R1为 C++17添加了新的重载。论文本身和其中的链接都没有讨论为什么只data被赋予了新的重载而c_str没有。在这一点上我们只能推测(除非参与讨论的人加入),但我想提供以下几点供考虑:

  • 即使只是添加重载来data破坏一些代码;保持这种变化保守是减少负面影响的一种方式。

  • c_str迄今为止,该函数与“C 字符串”(即不可变的、以空字符结尾的字符数组)data的接口代码的接口代码实际上是完全相同的,并且实际上是“遗留”工具。由于您始终可以替换为,因此没有特别的理由添加到此旧界面。c_strdata

我意识到 P0292R1 的真正动机是确实存在遗留 API,它们错误地或出于 C 的原因仅采用可变指针,即使它们不会发生变异。尽管如此,我想我们不想在绝对必要的字符串已经庞大的 API 中添加更多内容。

还有一点:从 C++17 开始,您现在可以写入终止符,只要您写入零值。(以前,它曾经是 UB 向空终止符写入任何内容。)一个可变的c_str将创建另一个进入这个特殊微妙之处的入口点,我们拥有的微妙之处越少越好。

于 2018-11-27T13:14:54.863 回答
5

它只取决于“你想用它做什么”的语义。一般来说,std::string有时用作缓冲区向量,即作为std::vector<char>. 这可以boost::asio经常看到。换句话说,它是一个字符数组。

c_str(): 严格来说意味着您正在寻找一个以空字符结尾的字符串。从这个意义上说,您永远不应该修改数据,也不应该将字符串作为非常量。

data():您可能需要将字符串内的信息作为缓冲区数据,甚至作为非常量。您可能需要也可能不需要修改数据,您可以这样做,只要它不涉及更改字符串的长度。

于 2018-11-27T13:12:22.073 回答
3

由于 std::string 类的历史,存在两个成员函数c_str和std::string 的数据。

在 C++11 之前,可以将 std::string 实现为写时复制。内部表示不需要存储字符串的任何空终止。成员函数c_str确保返回的字符串为空终止。成员函数data simlpy 返回一个指向存储字符串的指针,该指针不一定以 null 结尾。- 为确保注意到对字符串的更改以启用写时复制,这两个函数都需要返回指向 const 数据的指针。

当 std::string 不再允许写时复制时,C++11 改变了这一切。由于仍然需要c_str来传递以 null 结尾的字符串,因此 null 始终附加到实际存储的字符串中。否则,对c_str的调用可能需要更改存储的数据以使字符串为空终止,这将使c_str成为非常量函数。由于data传递一个指向存储字符串的指针,它通常具有与c_str相同的实现。由于向后兼容性,这两个功能仍然存在。

于 2018-11-27T20:25:51.750 回答