将流缓冲区的内容复制到字符串中

40

显然,boost::asio::async_read 不支持字符串,因为 boost::asio::buffer 的唯一重载允许我创建 const_buffer,所以我被迫将所有内容读入一个 streambuf 中。
现在我想将 streambuf 的内容复制到一个字符串中,但它似乎只支持向 char* 写入(sgetn()),需要通过使用 streambuf 创建 istream 并使用 getline() 来实现。

除了过度复制之外,有没有其他方法可以创建包含 streambuf 内容的字符串?

11个回答

51

我不知道是否算作“过度复制”,但你可以使用stringstream:

std::ostringstream ss;
ss << someStreamBuf;
std::string s = ss.str();

如果想将从stdin读取的所有内容存储到字符串中,可以使用以下代码:

std::ostringstream ss;
ss << std::cin.rdbuf();
std::string s = ss.str();

或者,您也可以使用istreambuf_iterator。您需要测量哪种方法更快 - 我不知道。

std::string s((istreambuf_iterator<char>(someStreamBuf)), 
               istreambuf_iterator<char>());

请注意,上面的someStreamBuf是表示一个streambuf*,所以必要时需要取其地址。此外,在最后一个示例中,注意第一个参数周围的额外括号,以避免将其解释为返回字符串并接受迭代器和另一个函数指针的函数声明(“最烦人的解析”)。


3
谢谢,istreambuf_iterator 正是我一直在寻找的。 - tstenner
1
这里有个奇怪的情况。我无法理解为什么,但是 istreambuf_iterator 会删掉最后的一些符号(如果最后一行超过20个符号)。有关于它可能的原因的任何想法吗? - Ben Usman
ss << someStreamBuf拷贝了11个字节的'0xFFFFFFFFF',它表示内存中streambuf的地址。谢谢,非常有帮助 :D - Alex Kremer
1
@AlexKremer 显然 someStreamBufPointer 是有意义的。 - sehe
自从C++11以来,“最令人烦恼的解析”问题也可以通过istreambuf_iterator<char>{someStreamBuf}避免。 - MSalters

50

这段文字中,“真正的”信息被在文档中深深埋藏...

如果有一个boost::asio::streambuf b,并且有一个size_t buf_size...

boost::asio::streambuf::const_buffers_type bufs = b.data();
std::string str(boost::asio::buffers_begin(bufs),
                boost::asio::buffers_begin(bufs) + buf_size);

5
这很有效!如果你想将整个内容放入字符串中,可以使用b.size()代替buf_size - Malvineous
1
假设tstenner的答案中所说的它被认为是不干净的,并且在“正常streambufs”中不起作用,是否适用于此答案?还是这种方式被认为是安全的? - ChrisPeterson

24

使用 boost::asio::streambuf 的另一种可能性是,结合使用 boost::asio::buffer_cast<const char*>()boost::asio::streambuf::data()boost::asio::streambuf::consume() 来实现:

const char* header=boost::asio::buffer_cast<const char*>(readbuffer.data());
//Do stuff with header, maybe construct a std::string with std::string(header,header+length)
readbuffer.consume(length);

这在普通的streambuf上不起作用,可能被认为是不规范的做法,但似乎是最快的方法。


长度从哪里来? - Trevor Hickey
这是你从流中取出的字节数。请参阅http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/reference/basic_streambuf/consume.html。 - tstenner
“normal streambufs”是什么意思?Sean DeNigris的回答基于文档不正确吗? - ChrisPeterson
2
只是提醒现在看到这个的人,这只适用于您的流有一个分配和填充的单个缓冲区对象。如果流有多个缓冲区,那么您就无法使用这种方法了。为了保证这种方法能够正常工作,您需要做的是获取缓冲区的迭代器,然后将迭代器转换。您可以通过访问buffer->data().begin()来获取这些迭代器。 - user562566
我找不到任何关于boost::asio::streambuf如何分配多个缓冲区对象的信息。您能告诉我streambuf返回多个缓冲区的要求是什么吗? - eclipse
1
@eclipse 你得标记人!我没看到你的评论,实际上我回到这里找到了自己的评论,因为我忘记了这个坑。无论如何,streambuf 内部将分配多个内部缓冲区,单个 streambuf 跨越这些缓冲区。为了安全起见,你必须绝对使用迭代器。像这样直接使用转换会给你一个指针,这将导致未定义的行为,无论是数据排序出错还是你用于从指针读取的长度超出了边界。 - user562566

16

如果您使用async_read_until,那么这个答案肯定更好,因为该调用可以返回多行数据,而此答案正确地将额外的数据留在了streambuf中。 - Jim Hunziker

2

使用std::basic_streambuf::sgetn,还可以从asio::streambuf中获取字符:

asio::streambuf in;
// ...
char cbuf[in.size()+1]; int rc = in.sgetn (cbuf, sizeof cbuf); cbuf[rc] = 0;
std::string str (cbuf, rc);

1

我没有看到有现成的答案可以读取恰好 n 个字符到 std::stringstream 中,因此这里是如何实现的:

std::stringstream ss;
boost::asio::streambuf sb;
const auto len = 10;

std::copy_n(boost::asio::buffers_begin(sb.data()), len,
            std::ostream_iterator<decltype(ss)::char_type>(ss));

编译器探索器


1

你只能从std::string创建const_buffer的原因是,std::string在其协议中明确不支持直接基于指针的写入。你可以做一些邪恶的事情,比如将字符串调整为特定大小,然后使用const_cast从c_str()中去除constness并将其视为原始char*缓冲区,但这是非常淘气的,迟早会让你陷入麻烦。

我使用std::vector作为我的缓冲区,因为只要向量不重新调整大小(或者你小心处理调整大小),你就可以很好地进行直接指针写入。如果我需要将一些数据作为std::string使用,我必须将其复制出来,但是我处理读取缓冲区的方式是,任何需要超出读取回调的内容都需要被复制出来。


0

我通常不喜欢那些回答:“你不需要X,你需要Y,这是如何做Y的”,但在这种情况下,我相信我知道tstenner想要什么。

在Boost 1.66中,添加了动态字符串缓冲区类型,因此async_read可以直接调整大小并写入字符串缓冲区。


你能提供一个例子吗?官方文档甚至没有展示如何构建一个,因为它是一种模板... - TheClockTwister

0
一个更简单的方法是将其转换为 std::string 并像这样进行操作。
 std::string buffer_to_string(const boost::asio::streambuf &buffer)
 {
  using boost::asio::buffers_begin;
  auto bufs = buffer.data();
  std::string result(buffers_begin(bufs), buffers_begin(bufs) + buffer.size());
 return result;
}

给出一个非常简洁的代码来完成任务。

这只是与Sean DeNigris相同的答案...他在4年前发布了它! - Arthur Tacca

-1

我尝试了第一个答案,但在使用“g++ -std=c++11”编译时出现了编译器错误。

对我有效的方法是:

        #include <string>
        #include <boost/asio.hpp>
        #include <sstream>           
        //other code ...
        boost::asio::streambuf response;
        //more code
        std::ostringstream sline;
        sline << &response; //need '&' or you a compiler error
        std::string line = sline.str(); 

这个已经编译并运行了。


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