取得streambuf / stringbuf数据的所有权

时间:2018-05-15 23:33:40

标签: c++ iostream ostream streambuf

我想要一个用于写入自动调整大小的数组的接口。一种方法是使用通用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类中获取数组的所有权并返回它?

我觉得这可以使用标准库来完成,因为流缓冲区甚至可能不是连续的数组。

3 个答案:

答案 0 :(得分:1)

IIRC存在stringstream(vs strstream)的全部原因是通过提供直接缓冲区访问来解决内存所有权的模糊问题。例如我认为改变是专门阻止你要求做的事情。

我认为您必须通过覆盖流缓冲区来实现这一目标。为了回答类似的问题,我为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();
}