取得 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();
}