我想要一个用于写入自动调整大小的数组的接口。一种方法是使用通用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类中获取数组的所有权并返回它?
我觉得这可以使用标准库来完成,因为流缓冲区甚至可能不是连续的数组。
答案 0 :(得分:1)
我认为您必须通过覆盖流缓冲区来实现这一目标。为了回答类似的问题,我为input streams提出了一些内容,这些内容最终得到了很多赞成。但说实话,我当时并不知道我在说什么,现在我还没有提出以下建议:
黑客攻击this link from the web以执行"大写的流缓冲区"一个只是回声并给你一个缓冲区的引用可能会给出:
#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;
}
我确定它以各种方式被打破。但是大写缓冲区的作者似乎认为单独覆盖overflow()方法会让它们将所有输出都大写为流,所以我想有人可能会说,如果写入一个&#,它足以看到所有输出# 39;自己的缓冲区。
但即便如此,一次去一个角色似乎不是最理想的......谁知道从一开始就从streambuf那里获得了多少开销。 请咨询离您最近的C ++ iostream专家,了解实际的正确方法。但希望能够证明某种类型的东西是可行的。
答案 1 :(得分:1)
如果您愿意使用旧的,已弃用的<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()
为您做好准备...
答案 2 :(得分:0)
这是我最终使用的解决方案。这个想法与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
// https://stackoverflow.com/questions/29132458/why-does-the-standard-have-both-seekpos-and-seekoff
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();
}