有没有一种方法可以获取std:string的缓冲区?

17

有没有一种方法可以获取std::string的“原始”缓冲区?
我考虑的是类似于的东西。举个例子,对于CString,我会这样做:

CString myPath;  
::GetCurrentDirectory(MAX_PATH+1, myPath.GetBuffer(MAX_PATH));  
myPath.ReleaseBuffer();  

那么,std::string有类似的东西吗?


可能是 https://dev59.com/yVzUa4cB1Zd3GeqP1DgO 的重复问题。 - Benj
参见:直接将char*缓冲区写入std::string - Gabriel Staples
更好的方法是查看:如何将std::string转换为const char*char* - Gabriel Staples
7个回答

22

虽然有点不寻常,但将std::string用作线性内存缓冲区是完全有效的,唯一需要注意的是在C++11之前的标准中并不支持它。

std::string s;
char* s_ptr = &s[0]; // get at the buffer

引用Herb Sutter的话:

在我所知的每个std::string实现中,它实际上都是连续的并以null结尾的。因此,虽然它在形式上不能保证,但在实践中您可能可以通过调用 &str[0] 来获取指向连续和以null结尾的字符串的指针。(但为了安全起见,您仍应使用str.c_str()。)

“可能”在这里是关键。因此,虽然这不是一项保证,但您应该能够依赖于std::string是线性内存缓冲区的原则,并且应该在测试套件中断言这些事实,以确保安全。

您总是可以构建自己的缓冲类,但当您想要购买时,这就是STL提供的东西。


2
str.c_str() 返回一个常量指针,因此肯定同样危险,如果不是更危险的话。 - paulm
1
我猜问题在于这是实现细节。并不以任何方式建议这是良好的做法,但如果您希望解决此限制,可以这样做。即使使用了小字符串优化,您也将获得一个安全写入的内存位置,只要您尊重数组的边界。您可能没有意识到它返回了指向堆栈的指针,但这不是您需要知道的事情。我无法想象出这个返回指针实际上指向守卫页的情况(如果您对其进行写入会导致页面错误)。 - John Leidegren
1
自C++11以来,这应该是被接受的答案。请参阅https://www.tomhuang.com/2011/10/24/using-std-string-as-the-output-buffer-in-c-api.html。 - Alexandr Zarubkin
还有一个非常重要的注意事项:如果你像使用char *一样向这个std::string缓冲区中写入内容,你必须使用s.resize(BUFFER_SIZE)预先分配缓冲区的大小,否则写入该缓冲区是未定义的行为。s.reserve(BUFFER_SIZE)不能满足需求。参见:https://en.cppreference.com/w/cpp/string/basic_string/operator_at:“如果`pos > size(),行为是未定义的。”因此,你可以使用s.resize()强制分配值为空终止字符的缓冲区,直到达到指定的大小。然后,你可以像正常的char *`一样在其大小范围内写入该缓冲区。 - Gabriel Staples
我在这里添加了一个答案,以详细解释我在上一条评论中所说的内容。 - Gabriel Staples

20

如果你想要一个真正的缓冲区,请使用std::vector<char>

#include <vector>
#include <string>

int main(){
  std::vector<char> buff(MAX_PATH+1);
  ::GetCurrentDirectory(MAX_PATH+1, &buff[0]);
  std::string path(buff.begin(), buff.end());
}

Ideone上的示例


3
如果您将std::vector用作缓冲区,则会以最昂贵的方式初始化向量中的每个元素。这将超过应用程序中的任何CPU成本。请使用std::unique_ptr<char[]>或堆栈分配。如果不需要初始化缓冲区,请勿浪费CPU资源。 - John Leidegren
3
我只能从自己的经验说起,建议你通过性能分析器运行程序并自行检查,但显然需要超过一个调用才能成为瓶颈。我提出这个原因是,如果将它放入你经常调用的库例程中,你会浪费很多CPU资源。此外,由于您在编译时知道MAX_PATH的值,因此可以将其堆栈分配,该缓冲区的持续时间很可能非常短。向量类代表“真实缓冲区”的概念,在任何方面上都是完全错误的。 - John Leidegren
1
请注意,std::vector<char> 不会像 memset(..., 0, sizeof ...) 那样聪明地执行任何操作,尽管后者速度更快,但前者会对每个元素进行适当的初始化。 - John Leidegren
2
@JohnLeidegren:内核切换到GetCurrentDirectory可能更昂贵。你不仅需要超过1个,而且需要大量的调用才能成为瓶颈。你的评论暗示它很可能是一个严重问题,但实际上它极不可能出现问题。 - Puppy
1
@DeadMG 我并不是在争论它是比内核转换更快/更慢。我想表达的是,作为一个缓冲区,它是不必要的慢。我的观点是,这是一个不好的习惯。即使像这样的小而看似不重要的代码也会积累起来,并最终对性能造成明显的影响。 - John Leidegren
显示剩余8条评论

3
不可移植的,不行。标准不能保证在内存中具有独占线性表示(而使用旧的C++03标准,甚至允许像绳子这样的数据结构),因此API不提供对其的访问权限。它们必须能够更改其内部表示(在C++03中)或提供对其线性表示的访问权限(如果它们有一个,这在C++11中得到了强制执行),但仅限于读取。您可以使用data()和/或c_str()来访问此内容。由于如此,接口仍然支持写时复制。
与指针访问数组并修改其内容的C-API一起使用时,通常建议使用std :: vector,因为它保证具有精确的线性内存表示,正是为了这个目的而设计的。
总之,如果您想以可移植的方式,并且希望您的字符串最终出现在中,则别无选择,只能将结果复制到该字符串中。

1
你参考的是哪个标准?C++11确实保证了这一点。再次参见此背景讨论 - sehe
我有点依赖于你如何处理对象身份。 是的,新标准确保字符串的线性表示(如char内存块)存在于内存中。 不,它不保证这是该字符串的唯一表示。 毕竟,写入复制实现仍符合规范,因此“data()”和“c_str()”仍然是const。 我会更新我的答案以反映该缓冲区的“独占性”。 - ltjax
1
COW在C++0x中已经过时,因为移动语义;而且自C++0x多线程规范以来,它非常不方便。在并发编程中,COW几乎从未带来性能上的好处(由于需要锁定),而且有太多的性能问题。事实上,我记得读到过C++11将禁止std::string的COW实现(但我仍然找不到链接)。 - sehe
我同意COW实现通常是愚蠢的,但规范看起来仍然允许它。虽然这与主题无关:虽然COW可以用于模拟移动语义,但即使只是为了节省内存,它仍然对存在于多个实例中的长字符串有益处。 - ltjax
@sehe 我会说并发编程是相当无用的东西,而COW则是在没有指针和引用的情况下高效传递字符串的有用工具。对于你的I/O,请使用异步操作,使用消息队列在进程之间通信,不要再考虑混乱的锁定! - Erik Aronesty
据我所知,自C++11起,COW实现被禁止了,不是吗? - Alexandr Zarubkin

2

根据MSDN文章所述, 我认为这是直接使用std::wstring做你想做的事情的最佳方法。第二好的方法是std::unique_ptr<wchar_t[]>,第三好的方法是使用std::vector<wchar_t>。请随意阅读文章并得出自己的结论。

// Get the length of the text string
// (Note: +1 to consider the terminating NUL)
const int bufferLength = ::GetWindowTextLength(hWnd) + 1;
// Allocate string of proper size
std::wstring text;
text.resize(bufferLength);
// Get the text of the specified control
// Note that the address of the internal string buffer
// can be obtained with the &text[0] syntax
::GetWindowText(hWnd, &text[0], bufferLength);
// Resize down the string to avoid bogus double-NUL-terminated strings
text.resize(bufferLength - 1);

1
 std::string str("Hello world");
 LPCSTR sz = str.c_str();

请记住,当str被重新分配或超出范围时,sz将无效。您可以像这样做以与字符串解耦:
 std::vector<char> buf(str.begin(), str.end()); // not null terminated
 buf.push_back(0); // null terminated

或者,按照老式的C风格(请注意,这将不允许包含嵌入空字符的字符串):

 #include <cstring>

 char* sz = strdup(str.c_str());

 // ... use sz

 free(sz);

@sehe:我并不是想挑起争端。我只是在这个网站上读到了太多没有带有“winapi”标签的LPCSTR和其他Windows-ism的帖子。 - Fred Foo
据我所知,标记“winapi”并非强制要求。此外,我们可以添加它(事实上,我会开始这样做,因为现在我了解到了它)。 - sehe
@sehe:好的。希望你不要生气?我后来才意识到我的话可能听起来很严厉。在互联网上表达讽刺是很难的,所以我一直在发现:) - Fred Foo
1
哦,看到你更新的答案:如果我没记错,在Windows平台上没有strdup函数;它在那里被称为_strdup - Fred Foo

1
它有 c_str ,在我所知道的所有C ++实现中都返回底层缓冲区(但作为 const char *,因此您无法修改它)。

《C++程序设计语言》第三版中提到string::data()函数:“将字符串的字符写入数组并返回指向该数组的指针”。关于c_str函数:“c_str()函数类似于data(),但它在末尾添加了一个0(零)[...]”。因此它们返回的是副本(尽管实现者决定返回缓冲区的指针)。 - MikMik
1
@MikMik:在所有已知的实现中,它都返回缓冲区。此外,根据新的C++11标准,由于复杂度要求,这是必需的(隐含地)-请参见此说明 - sehe
1
我不是专家,但“在所有已知的实现中它返回缓冲区”并不意味着“它必须返回缓冲区”。现在,在C++11中是否需要这样做?好知道,但我还没有使用C++11。无论如何,如果我现在不能写入缓冲区,它对我没有任何帮助。 - MikMik

-1

我认为纯粹主义者会对你这样做表示不满。无论如何,如果你想要一个动态字符串类型,可以轻松地传递给低级API函数,并且在同一时间修改其缓冲区和大小,而不需要进行任何转换,那么最好不要依赖臃肿和通用的标准库,而是自己实现它!实际上,这是一个非常具有挑战性和有趣的任务。例如,在我的自定义txt类型中,我重载了这些运算符:

ui64 operator~() const; // Size operator
uli32 * operator*();    // Size modification operator
ui64 operator!() const; // True Size Operator
txt& operator--();      // Trimm operator

还有这个强制转换:

operator const char *() const;
operator char *();

因此,我可以直接将txt类型传递给低级API函数,甚至不需要调用任何.c_str()。然后,我还可以传递API函数的真实大小(即缓冲区的大小)和指向内部大小变量的指针(operator*()),以便API函数可以更新写入的字符数,从而无需调用stringlength就能得到有效字符串!

我试图通过这个txt来模拟基本类型,因此它根本没有公共函数,所有公共接口都是通过运算符进行的。这样,我的txtint和其他基本类型完美匹配。


我对这个答案的问题在于:如果标准库不能满足您的需求,定义一个新类是完全合理的。然而,您没有展示您的新类的实现(以避免标准库版本的臃肿)。相反,您告诉我们一组奇怪的运算符重载,使得这个类的行为像世界上没有其他类一样(否定“大小”!?)。 - DavidW

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