返回类型方面,c_str()和data()有什么区别?

39

在C++11之后,我认为c_str()data()是等效的。

C++17为后者引入了一个重载版本,返回非常量指针 (参考,我不确定它是否完全更新了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
我猜这与 c_str 是以空字符结尾有关,而 std::string 可能在中间包含空字符,我也希望 data() 返回原始缓冲区(无论其中间是否包含空字符)。 - 463035818_is_not_a_number
@user463035818,它们在我制作的这个糟糕的示例中返回相同的结果... - gsamaras
可能是[为什么string :: data()不提供可变的char *?]的重复问题(https://dev59.com/X1sX5IYBdhLWcg3wLM3K)。 - Jonathan Mee
@JonathanMee 谢谢分享,但这回答我的问题在哪里呢?从我在这里看到的回答中,"我们只能猜测"。我不明白这是怎么个重复了,但如果我错了,请告诉我。 :) - gsamaras
1
不是@JonathanMee,我的问题是为什么这些方法不是同质的,因为C++11朝着这个方向发展! ;)如果我问你的想法,那肯定会是重复的。现在的问题是为什么data()有一个重载,而c_str()没有?我的意思是我们在C++11中使它们同质化,现在我们又区分它们,为什么?这种问题在重复中没有得到回答。然而,这里的答案提供了对我的问题的见解! - gsamaras
显示剩余3条评论
4个回答

23
新的重载是由P0272R1为C++17添加的。既没有论文本身,也没有其中的链接讨论为什么只给data提供了新的重载,而没有给c_str提供。在这一点上,我们只能进行推测(除非参与讨论的人加入进来),但我想提出以下几点供考虑:
  • 即使只是向data添加重载也会破坏一些代码;保持这个改变的保守性是最小化负面影响的一种方式。

  • c_str函数到目前为止完全与data相同,实际上是用于接口代码的“遗留”设施,该代码采用“C字符串”,即一个不可变的、以null结尾的字符数组。由于你可以随时用data替换c_str,所以没有特别的理由添加到这个遗留接口。

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

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


@gsamaras:没问题——我添加了一条关于写入空终止符的注释。 - Kerrek SB
1
@rustyx:新的data重载绝对*破坏了代码。我们应对了,但这不是你想要随意做的事情。 - Kerrek SB
1
@KerrekSB 昨晚我在睡觉时想到了你的第一个问题。为什么非 const 重载会破坏事情呢?我的意思是,在需要 const 的地方,方法的相关 const 重载会被调用,不是吗? - gsamaras
1
@gsamaras:没错,每个提出这个想法并认为它很简单的人都是这么想的。然后真正的实现者将更改部署到真正的代码库中,就会发生故障。问题在于,真实代码库中的真实用户会做一些相当奇怪的事情,这是你从未想过的 :-S - Kerrek SB
有些东西告诉我,今晚在睡梦中我会想到什么。@KerrekSB,这种超载会导致什么问题!但也许我会把它留到另一个问题中。 - gsamaras
显示剩余2条评论

23
data()成员函数被重载的原因在于这篇论文中进行了解释。阅读文章可以了解更多信息。
该论文的TL;DR概述如下:为了改善标准库的统一性并帮助C++开发人员编写正确的代码,增加了std::string的非const .data()成员函数。在调用C库函数时,如果其C字符串参数没有const限定符,使用它也很方便。
以下是论文中的一些相关段落:

摘要
std::string缺乏非const .data()成员函数是一个疏忽还是基于C++11之前std::string语义的有意设计?无论哪种情况,这种功能的缺失诱使开发人员在几个合法的场景下使用不安全的替代方式。本文主张为std::string增加非const .data()成员函数,以改善标准库的统一性并帮助C++开发人员编写正确的代码。

用例
C库有时包括具有char *参数的例程。 Windows API中CreateProcess函数的lpCommandLine参数就是一个例子。由于std::stringdata()成员是const,它不能用于将std :: string对象与lpCommandLine参数一起使用。开发人员会尝试使用.front()来代替,例如以下示例。

std::string programName;
// ...
if( CreateProcess( NULL, &programName.front(), /* etc. */ ) ) {
  // etc.
} else {
  // handle error
}
请注意,当 programName 为空时,programName.front() 表达式会导致未定义的行为。一个临时的空 C-字符串可以修复这个 bug。
std::string programName;
// ...

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

如果有一个非const的.data()成员,就像std::vector一样,正确的代码将是直截了当的。

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

当调用没有在其C字符串参数上具有const限定符的C库函数时,非常方便的是一个非const .data() std::string成员函数。这在旧代码和需要与旧的C编译器兼容的代码中很常见。


5

这取决于“您想对其进行的操作”的语义。 一般来说,std::string 有时被用作缓冲向量,即替代 std::vector<char>。 这经常在 boost::asio 中见到。 换句话说,它是一个字符数组。

c_str():严格意义上意味着您正在寻找以空字符结尾的字符串。 在这种意义上,您不应修改数据,并且您永远不需要将字符串作为非const使用。

data():您可能需要将字符串中的信息作为缓冲区数据使用,甚至可以作为非const使用。 您可能需要修改数据,但只要不涉及更改字符串的长度即可执行该操作。


3
我认为这里的空终止符并不重要。就空终止而言,c_strdata是完全等价的。 - Kerrek SB
1
@KerrekSB是正确的,在C++11之后,这两种方法都返回以空字符结尾的字符串。 - gsamaras
2
@KerrekSB 这不是关于空终止符是否存在的问题。而是关于你想要“空终止字符串”还是“缓冲向量”,在后者中你并不关心空终止符。 - The Quantum Physicist
@TheQuantumPhysicist:是的,我理解你的观点,但我想要澄清一下这个观念,即你不应该使用data来请求空终止符(这可能是你想要暗示的,也可能不是)。完全可以使用data来明确获取一个以空字符结尾的字符串;我不会建议任何人改用c_str - Kerrek SB
2
@KerrekSB 你说得没错,但要记住C++是一种表达性语言,你编写的代码文本应该理想地具有意义。个人认为,如果你只想要一个以空字符结尾的字符串,使用data()是不好的实践。你不会帮助下一个阅读你代码的人。这是我的观点,无论如何 :-) - The Quantum Physicist
显示剩余2条评论

3

std::string类的两个成员函数c_strdata由于其历史原因而存在。

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

所有这些都在C++11中发生了变化,因为std::string不再允许使用写时复制。由于c_str仍然需要提供空终止的字符串,因此空终止符总是附加到实际存储的字符串末尾。否则,调用c_str可能需要更改存储的数据以使字符串以空终止符结尾,这将使c_str成为一个非const函数。由于data提供了指向存储字符串的指针,因此它通常与c_str具有相同的实现。这两个函数由于向后兼容性而仍然存在。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接