使用C函数操作std::string

4
有时候你需要使用C函数构造的字符来填充一个std::string。一个典型的例子是这样的:
constexpr static BUFFERSIZE{256};
char buffer[BUFFERSIZE];
snprint (buffer, BUFFERSIZE, formatstring, value1, value2);
return std::string(buffer);

请注意,我们首先需要填充本地缓冲区,然后将其复制到std::string中。
如果最大缓冲区大小是计算出来的,而不一定是您想要存储在堆栈上的内容,则示例变得更加复杂。例如:
constexpr static BUFFERSIZE{256};
if (calculatedBufferSize>BUFFERSIZE)
   {
   auto ptr = std::make_unique<char[]>(calculatedBufferSize);
   snprint (ptr.get(), calculatedBufferSize, formatstring, value1, value2);
   return std::string(ptr.get());
   }
else
   {
   char buffer[BUFFERSIZE];
   snprint (buffer, BUFFERSIZE, formatstring, value1, value2);
   return std::string(buffer);
   }

这使得代码变得更加复杂,如果calculatedBufferSize大于我们想要的堆栈大小,我们实际上会执行以下操作:
  • 分配内存(make_unique)
  • 将内存填充为所需结果
  • 分配内存(std::string)
  • 将内存复制到字符串中
  • 释放内存
自C++17以来,std::string有一个非const的data()方法,这意味着这是操作字符串的方式。因此,这样做似乎很诱人:
std::string result;
result.resize(calculatedBufferSize);
snprint (result.data(), calculatedBufferSize, formatstring, value1, value2);
result.resize(strlen(result.c_str()));
return result;

我的实验表明,最后的resize是必要的,以确保字符串的长度正确报告。std::string::length()不会搜索nul终止符,它只返回大小(就像std::vector一样)。
请注意,我们的分配和复制要少得多:
  • 分配内存(调整字符串大小)
  • 用所需结果填充内存
老实说,尽管它看起来更有效率,但对我来说它也非常“非标准”。有人可以指出这是否是C++17标准允许的行为吗?还是有其他更有效的方式进行这种操作?
请不要参考问题Manipulating std::string,因为那个问题涉及到更加混乱的逻辑(甚至使用了memset)。同时,请不要回答我必须使用C++流(std::string_stream,高效吗?老实说?)。有时候你只是想重用C语言中的高效逻辑。

1
为什么会这样啊 :o - Konrad
2
那么为什么你的 calculatedBufferSize 不等于 strlen(result) 呢? - Zang MingJie
2
如果你不想让代码看起来“不规范”,那就不要混用C和C++。要么使用C++函数进行字符串操作,要么继续使用C,但不要将其转换回std::string,并继续使用char*进行代码编写(这似乎更有意义,因为你想知道的是以空字符结尾的字符串长度而不是实际数据长度)。 - markus-nm
使用asprintf来分配缓冲区,然后用它来初始化std::string。或者调用两次sprintf。 - KamilCuk
1
@Patrick 提供的示例可以和应该被替换为更安全的内容。C格式字符串容易出错,而且通常很难猜测正确的占位符类型,特别是对于cstdint类型(如果你不了解宏定义的话)。我看了一下效率,发现boost比较慢。但是还有其他的库,比如FastFormat等。 - KIIV
显示剩余8条评论
2个回答

7
修改由data()指向的内容是可以的,假设您不将data() + size()处的值设置为除null字符以外的任何值。来自[string.accessors]

charT* data() noexcept;

返回:一个指针p,使得对于[0,size()]中的每个ip + i == addressof(operator[](i))

复杂度:常数时间。

备注:程序不得将存储在p + size()处的值修改为除charT()以外的任何值;否则,行为未定义。


尽管如此,语句result.resize(strlen(result.c_str()));看起来有点奇怪。使用std::snprintf返回写入的字符数来调整字符串大小更合适。此外,用正确的大小构造字符串比构造一个立即被调整大小的空字符串看起来更整洁:
std::string result(maxlen, '\0');
result.resize(std::max(0, std::snprintf(result.data(), maxlen, fmt, value1, value2)));
return result;

4

总的来说,这个方法看起来不错。我建议做出一些改变。

  1. 捕获snprinf的返回值。
  2. 使用它来执行错误检查并避免调用strlen

std::string result;
result.resize(calculatedBufferSize);
int n = snprint (result.data(), calculatedBufferSize, formatstring, value1, value2);

if ( n < 0 )
{
   // Problem. Deal with the error.
}

result.resize(n);
return result;

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