取得 streambuf/stringbuf 数据的所有权
Taking ownership of streambuf/stringbuf data
我想要一个用于写入自动调整大小数组的接口。一种方法是使用通用 std::ostream *
.
然后考虑ostringstream
是否是目标:
void WritePNG(ostream *out, const uint8_t *pixels);
void *WritePNGToMemory(uint8_t *pixels)
{
ostringstream out;
WritePng(&out, pixels);
uint8_t *copy = new uint8_t[out.tellp()];
memcpy(copy, out.str().c_str(), out.tellp()];
return copy;
}
但我想避免使用 memcpy()。有没有办法在底层 stringbuf class 和 return 中获取数组的所有权?
我觉得使用标准库无法做到这一点,因为流缓冲区甚至可能不是连续数组。
IIRC stringstream 存在的全部原因(与 strstream 相比)是为了解决内存所有权的模糊问题,这些问题会通过提供直接缓冲区访问来解决。例如我认为该更改是为了专门阻止您要求执行的操作。
无论哪种方式,我认为您都必须通过覆盖流缓冲区来自己完成。为了回答类似的问题,我为 input streams 提出了一些建议,最终获得了相当多的赞成票。但老实说,当时我不知道我在说什么,现在当我提出以下建议时也不知道:
黑客 this link from the web 对一个只是回应并给你一个缓冲区引用的 "uppercasing stream buffer" 可能会给出:
#include <iostream>
#include <streambuf>
class outbuf : public std::streambuf {
std::string data;
protected:
virtual int_type overflow (int_type c) {
if (c != EOF)
data.push_back(c);
return c;
}
public:
std::string& get_contents() { return data; }
};
int main() {
outbuf ob;
std::ostream out(&ob);
out << "some stuff";
std::string& data = ob.get_contents();
std::cout << data;
return 0;
}
我敢肯定它在各个方面都有问题。但是 uppercase-buffer-authors 似乎认为单独覆盖 overflow() 方法会让他们将所有输出大写到流中,所以我想有人可能会争辩说,如果写入自己的缓冲区就足以看到所有输出。
但即便如此,一次只选择一个字符似乎不是最佳选择……而且谁知道一开始从 streambuf 继承得到的开销是多少。 向离你最近的 C++ iostream 专家咨询真正正确的方法是什么。但希望它能证明这种事情是可能的。
如果您愿意使用旧的、已弃用的 <strstream>
界面,这很简单 - 只需创建一个 std::strstreambuf
指向您的存储,它就会神奇地工作。 std::ostrstream
甚至有一个构造函数可以为您完成此操作:
#include <iostream>
#include <strstream>
int main()
{
char copy[32] = "";
std::ostrstream(copy, sizeof copy) << "Hello, world!"
<< std::ends << std::flush;
std::cout << copy << '\n';
}
使用更现代的 <sstream>
接口,您需要访问字符串流的缓冲区,并调用 pubsetbuf()
使其指向您的存储:
#include <iostream>
#include <sstream>
int main()
{
char copy[32] = "";
{
std::ostringstream out{};
out.rdbuf()->pubsetbuf(copy, sizeof copy);
out << "Hello, world!" << std::ends << std::flush;
}
std::cout << copy << '\n';
}
显然,在这两种情况下,您都需要一种方法来提前知道要为 copy
分配多少内存,因为您不能等到 tellp()
为您准备好。 ..
这是我最终使用的解决方案。这个想法与 HostileFork 提出的想法相同——只需要实现 overflow()。但正如已经暗示的那样,它通过缓冲具有更好的吞吐量。它还可选地支持随机访问(seekp()、tellp())。
class MemoryOutputStreamBuffer : public streambuf
{
public:
MemoryOutputStreamBuffer(vector<uint8_t> &b) : buffer(b)
{
}
int_type overflow(int_type c)
{
size_t size = this->size(); // can be > oldCapacity due to seeking past end
size_t oldCapacity = buffer.size();
size_t newCapacity = max(oldCapacity + 100, size * 2);
buffer.resize(newCapacity);
char *b = (char *)&buffer[0];
setp(b, &b[newCapacity]);
pbump(size);
if (c != EOF)
{
buffer[size] = c;
pbump(1);
}
return c;
}
#ifdef ALLOW_MEM_OUT_STREAM_RANDOM_ACCESS
streampos MemoryOutputStreamBuffer::seekpos(streampos pos,
ios_base::openmode which)
{
setp(pbase(), epptr());
pbump(pos);
// GCC's streambuf doesn't allow put pointer to go out of bounds or else xsputn() will have integer overflow
// Microsoft's does allow out of bounds, so manually calling overflow() isn't needed
if (pptr() > epptr())
overflow(EOF);
return pos;
}
// redundant, but necessary for tellp() to work
//
streampos MemoryOutputStreamBuffer::seekoff(streamoff offset,
ios_base::seekdir way,
ios_base::openmode which)
{
streampos pos;
switch (way)
{
case ios_base::beg:
pos = offset;
break;
case ios_base::cur:
pos = (pptr() - pbase()) + offset;
break;
case ios_base::end:
pos = (epptr() - pbase()) + offset;
break;
}
return seekpos(pos, which);
}
#endif
size_t size()
{
return pptr() - pbase();
}
private:
std::vector<uint8_t> &buffer;
};
他们说好的程序员是懒惰的,所以这是我想出的替代实现,它需要更少的自定义代码。但是,存在内存泄漏的风险,因为它会劫持 MyStringBuffer 中的缓冲区,但不会释放 MyStringBuffer。实际上,它不会泄漏 GCC 的 streambuf,我使用 AddressSanitizer 确认了这一点。
class MyStringBuffer : public stringbuf
{
public:
uint8_t &operator[](size_t index)
{
uint8_t *b = (uint8_t *)pbase();
return b[index];
}
size_t size()
{
return pptr() - pbase();
}
};
// caller is responsible for freeing out
void Test(uint8_t *&_out, size_t &size)
{
uint8_t dummy[sizeof(MyStringBuffer)];
new (dummy) MyStringBuffer; // construct MyStringBuffer using existing memory
MyStringBuffer &buf = *(MyStringBuffer *)dummy;
ostream out(&buf);
out << "hello world";
_out = &buf[0];
size = buf.size();
}
我想要一个用于写入自动调整大小数组的接口。一种方法是使用通用 std::ostream *
.
然后考虑ostringstream
是否是目标:
void WritePNG(ostream *out, const uint8_t *pixels);
void *WritePNGToMemory(uint8_t *pixels)
{
ostringstream out;
WritePng(&out, pixels);
uint8_t *copy = new uint8_t[out.tellp()];
memcpy(copy, out.str().c_str(), out.tellp()];
return copy;
}
但我想避免使用 memcpy()。有没有办法在底层 stringbuf class 和 return 中获取数组的所有权?
我觉得使用标准库无法做到这一点,因为流缓冲区甚至可能不是连续数组。
IIRC stringstream 存在的全部原因(与 strstream 相比)是为了解决内存所有权的模糊问题,这些问题会通过提供直接缓冲区访问来解决。例如我认为该更改是为了专门阻止您要求执行的操作。
无论哪种方式,我认为您都必须通过覆盖流缓冲区来自己完成。为了回答类似的问题,我为 input streams 提出了一些建议,最终获得了相当多的赞成票。但老实说,当时我不知道我在说什么,现在当我提出以下建议时也不知道:
黑客 this link from the web 对一个只是回应并给你一个缓冲区引用的 "uppercasing stream buffer" 可能会给出:
#include <iostream>
#include <streambuf>
class outbuf : public std::streambuf {
std::string data;
protected:
virtual int_type overflow (int_type c) {
if (c != EOF)
data.push_back(c);
return c;
}
public:
std::string& get_contents() { return data; }
};
int main() {
outbuf ob;
std::ostream out(&ob);
out << "some stuff";
std::string& data = ob.get_contents();
std::cout << data;
return 0;
}
我敢肯定它在各个方面都有问题。但是 uppercase-buffer-authors 似乎认为单独覆盖 overflow() 方法会让他们将所有输出大写到流中,所以我想有人可能会争辩说,如果写入自己的缓冲区就足以看到所有输出。
但即便如此,一次只选择一个字符似乎不是最佳选择……而且谁知道一开始从 streambuf 继承得到的开销是多少。 向离你最近的 C++ iostream 专家咨询真正正确的方法是什么。但希望它能证明这种事情是可能的。
如果您愿意使用旧的、已弃用的 <strstream>
界面,这很简单 - 只需创建一个 std::strstreambuf
指向您的存储,它就会神奇地工作。 std::ostrstream
甚至有一个构造函数可以为您完成此操作:
#include <iostream>
#include <strstream>
int main()
{
char copy[32] = "";
std::ostrstream(copy, sizeof copy) << "Hello, world!"
<< std::ends << std::flush;
std::cout << copy << '\n';
}
使用更现代的 <sstream>
接口,您需要访问字符串流的缓冲区,并调用 pubsetbuf()
使其指向您的存储:
#include <iostream>
#include <sstream>
int main()
{
char copy[32] = "";
{
std::ostringstream out{};
out.rdbuf()->pubsetbuf(copy, sizeof copy);
out << "Hello, world!" << std::ends << std::flush;
}
std::cout << copy << '\n';
}
显然,在这两种情况下,您都需要一种方法来提前知道要为 copy
分配多少内存,因为您不能等到 tellp()
为您准备好。 ..
这是我最终使用的解决方案。这个想法与 HostileFork 提出的想法相同——只需要实现 overflow()。但正如已经暗示的那样,它通过缓冲具有更好的吞吐量。它还可选地支持随机访问(seekp()、tellp())。
class MemoryOutputStreamBuffer : public streambuf
{
public:
MemoryOutputStreamBuffer(vector<uint8_t> &b) : buffer(b)
{
}
int_type overflow(int_type c)
{
size_t size = this->size(); // can be > oldCapacity due to seeking past end
size_t oldCapacity = buffer.size();
size_t newCapacity = max(oldCapacity + 100, size * 2);
buffer.resize(newCapacity);
char *b = (char *)&buffer[0];
setp(b, &b[newCapacity]);
pbump(size);
if (c != EOF)
{
buffer[size] = c;
pbump(1);
}
return c;
}
#ifdef ALLOW_MEM_OUT_STREAM_RANDOM_ACCESS
streampos MemoryOutputStreamBuffer::seekpos(streampos pos,
ios_base::openmode which)
{
setp(pbase(), epptr());
pbump(pos);
// GCC's streambuf doesn't allow put pointer to go out of bounds or else xsputn() will have integer overflow
// Microsoft's does allow out of bounds, so manually calling overflow() isn't needed
if (pptr() > epptr())
overflow(EOF);
return pos;
}
// redundant, but necessary for tellp() to work
//
streampos MemoryOutputStreamBuffer::seekoff(streamoff offset,
ios_base::seekdir way,
ios_base::openmode which)
{
streampos pos;
switch (way)
{
case ios_base::beg:
pos = offset;
break;
case ios_base::cur:
pos = (pptr() - pbase()) + offset;
break;
case ios_base::end:
pos = (epptr() - pbase()) + offset;
break;
}
return seekpos(pos, which);
}
#endif
size_t size()
{
return pptr() - pbase();
}
private:
std::vector<uint8_t> &buffer;
};
他们说好的程序员是懒惰的,所以这是我想出的替代实现,它需要更少的自定义代码。但是,存在内存泄漏的风险,因为它会劫持 MyStringBuffer 中的缓冲区,但不会释放 MyStringBuffer。实际上,它不会泄漏 GCC 的 streambuf,我使用 AddressSanitizer 确认了这一点。
class MyStringBuffer : public stringbuf
{
public:
uint8_t &operator[](size_t index)
{
uint8_t *b = (uint8_t *)pbase();
return b[index];
}
size_t size()
{
return pptr() - pbase();
}
};
// caller is responsible for freeing out
void Test(uint8_t *&_out, size_t &size)
{
uint8_t dummy[sizeof(MyStringBuffer)];
new (dummy) MyStringBuffer; // construct MyStringBuffer using existing memory
MyStringBuffer &buf = *(MyStringBuffer *)dummy;
ostream out(&buf);
out << "hello world";
_out = &buf[0];
size = buf.size();
}