将以空字符结尾的字符数组复制到std::string中,同时考虑缓冲区长度。

20

也许只是因为缺少咖啡,但我正在尝试从已知最大长度的以空字符结尾的char数组中创建std::string,但我不知道该怎么做。

auto s = std::string(buffer, sizeof(buffer));

..是我最喜欢的候选人,但由于C++字符串不以null结尾,因此此命令将复制sizeof(buffer)个字节,而不考虑其中包含的任何'\0'。

结果为:

..是我最喜欢的候选人,但由于C++字符串不以null结尾,因此此命令将复制sizeof(buffer)个字节,而不考虑其中包含的任何'\0'。

auto s = std::string(buffer);

buffer 复制,直到找到 \0。这几乎是我想要的,但我不能信任接收缓冲区,所以我想提供最大长度。

当然,现在我可以这样整合 strnlen()

auto s = std::string(buffer, strnlen(buffer, sizeof(buffer)));

但这似乎很糟糕 - 它会两次遍历缓冲区,而且我必须处理 C 语言的工具库,比如 string.hstrnlen()(而且这很难看)。

在现代 C++ 中,我应该如何实现这个功能?


4
它要么是以空字符结尾,要么长度恰好为 sizeof(buffer),两者不能同时存在。 - n. m.
1
这不是真的(我对点赞感到惊讶)。提供给任何写入函数的C缓冲区可以预先分配固定大小。写入缓冲区的序列仍然可以以空终止符结尾。 - frans
1
无论如何,这只是一个小问题。正确的术语应该是“可能为null结尾的长度为N的数组”或类似的东西。 - n. m.
4
针对这种情况,我会预先分配一个额外的字符空间 char buffer[n+1]; buffer[n] = 0;,然后使用 std::string(buffer) 来生成字符串,因为它始终以空字符结尾。 - jingyu9575
2
或者只需将 null 放在缓冲区的最后一个字节中。最坏的情况是,你会失去一个已经被截断的字符串的一个字符。 - n. m.
显示剩余4条评论
3个回答

25
const char* end = std::find(buffer, buffer + sizeof(buffer), '\0');
std::string s(buffer, end);

它难道不仍然会两次遍历缓冲区吗? - songyuanyao
3
根据不同的角度,是的(需要进行两次遍历,一次查找长度,一次复制字节)。但这是不可避免的,因为在检查长度之前,您无法知道正确的分配大小,并且在分配内存之前无法复制字节。真正的一次遍历解决方案将始终分配sizeof(buffer),而不管实际内容有多短,这可能会显著增加内存消耗。 - John Zwinck
4
换句话说,std::string s(buffer); 已经对缓冲区进行了两次遍历,而且额外的大小限制在不允许过度分配的情况下也没有用。原则上,您可以按指数增加的大小进行一系列分配(类似于 vector),但是当您将所有中间副本相加时,仍然大致遍历了数据两次。 - Steve Jessop
@joeeey:这是一个演示,代码编译干净,所有编译器警告都已启用:https://godbolt.org/z/r56j5x - 如果您对其他版本有问题,请随时发布您的代码。 - John Zwinck
1
@joeeey 这是因为 bufferend 需要是相同的类型。可以使用 auto end 或者像这样移除 const: https://godbolt.org/z/3TzTaK - John Zwinck
显示剩余2条评论

0
如果您想要一种单次解决方案,请从以下内容开始:
template<class CharT>
struct smart_c_string_iterator {
  using self=smart_c_string_iterator;
  std::size_t index = 0;
  bool is_end = true;
  CharT* ptr = nullptr;
  smart_c_string_iterator(CharT* pin):is_end(!pin || !*pin), ptr(pin) {}
  smart_c_string_iterator(std::size_t end):index(end) {}
};

现在,将其修饰并使其成为完全的随机访问迭代器。大多数操作非常简单(例如 ++ 应同时推进 ptr 和 index ),除了 == 和!= 。
friend bool operator==(self lhs, self rhs) {
  if (lhs.is_end&&rhs.is_end) return true;
  if (lhs.index==rhs.index) return true;
  if (lhs.ptr==rhs.ptr) return true;
  if (lhs.is_end && rhs.ptr && !*rhs.ptr) return true;
  if (rhs.is_end && lhs.ptr && !*lhs.ptr) return true;
  return false;
}
friend bool operator!=(self lhs, self rhs) {
  return !(lhs==rhs);
}

我们还需要:
template<class CharT>
std::pair<smart_c_string_iterator,smart_c_string_iterator>
smart_range( CharT* ptr, std::size_t max_length ) {
  return {ptr, max_length};
}

now we do this:

auto r = smart_range(buffer, sizeof(buffer));
auto s = std::string(r.first, r.second);

在每个步骤中,我们复制时都会检查缓冲区长度和空终止符。

现在,Ranges v3引入了sentinal的概念,它可以让您以更少的运行时成本执行类似上述的操作。或者您可以手工制作等效的解决方案。


0
像这样的东西可以在单次通过中工作。
auto eos = false;
std::string s;
std::copy_if(buffer, buffer + sizeof(buffer), std::back_inserter(s),
  [&eos](auto v) {
    if (!eos) {
      if (v) {
        return true;
      }
      eos = true;
    }
    return false;
  });

5
除了在重新分配内存时需要额外复制字符所需的步骤外,还有什么别的? - T.C.
@T.C 如果不知道 buffer 的大小,可能很难猜测是否会进行任何重新分配。 - Nim

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