【问题标题】:Taking ownership of streambuf/stringbuf data获取 streambuf/stringbuf 数据的所有权
【发布时间】:2018-05-15 23:33:40
【问题描述】:

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

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

【问题讨论】:

  • 没有。你根本不应该坚持 c_str() 返回的值。

标签: c++ iostream ostream streambuf


【解决方案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';
}

使用更现代的&lt;sstream&gt; 接口,您需要访问字符串流的缓冲区,并调用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() 为您准备好...

【讨论】:

  • 巧妙的技巧,但我的输出大小未知(希望小于未压缩大小)。但我认为你在做某事。您可以使用 pubsetbuf() 或将一些内部变量设置为 NULL 来欺骗 streambuf 认为没有要释放的内存吗?
  • 我认为唯一合理的选择是自己实现一个缓冲区 - 从 std::basic_stringbuf 继承并使用 std::vector 作为存储 - 请阅读 setp 以获取使用 std::array 的示例。因为向量可以在调整大小时移动其内容,所以在使用 push_back 或任何其他使迭代器无效的向量方法后,您需要覆盖 overflow() 以调用 setp
【解决方案2】:

IIRC stringstream 存在的全部原因(与 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;
}

我确信它已经以各种方式损坏了。但是大写缓冲区的作者似乎认为单独覆盖溢出()方法会让他们将所有输出大写到流中,所以我想有人可能会争辩说,如果写入自己的缓冲区就足以看到所有输出。

但即便如此,一次只使用一个字符似乎不是最理想的……谁知道一开始从 streambuf 继承会产生什么开销。 请咨询离您最近的 C++ iostream 专家,了解真正正确的方法是什么。 但希望这能证明这种方法是可能的。

【讨论】:

    【解决方案3】:

    这是我最终使用的解决方案。这个想法和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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-12
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-11
      相关资源
      最近更新 更多