Получение права собственности на данные streambuf / stringbuf - PullRequest
0 голосов
/ 16 мая 2018

Мне нужен интерфейс для записи в автоматически изменяемый размер массива.Один из способов сделать это с помощью универсального 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 голосов
/ 16 мая 2018

Если вы хотите использовать старый устаревший интерфейс <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() не станетготов для вас ...

0 голосов
/ 28 июля 2018

Вот решение, которое я в итоге использовал. Идея такая же, как и предложенная 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, что я подтвердил с помощью 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();
}
0 голосов
/ 16 мая 2018

Во IIRC вся причина, по которой существует stringstream (против strstream), заключалась в том, чтобы разобраться в нечетких вопросах владения памятью, которые возникли бы путем предоставления прямого доступа к буферу.например, я думаю, что это изменение должно было специально предотвратить то, что вы просите сделать.

Так или иначе, я думаю, вам придется сделать это самостоятельно, переопределив буфер потока.Чтобы ответить на аналогичный вопрос, я предложил что-то для входных потоков , которое получило довольно много голосов.Но, честно говоря, я не знал, о чем говорил, ни сейчас, когда я предлагал следующее:

Взлом этой ссылки из Интернета для создания "буфера потока верхнего регистра" длятот, который просто повторяет и дает ссылку на свой буфер, может дать:

#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 () позволило бы им прописать весь вывод в поток, так что я думаю, можно утверждать, что достаточно увидеть все выходные данные при записи в собственный буфер.

Но даже в этом случае переход по одному символу за раз кажется неоптимальным ... и кто знает, какие накладные расходы вы получаете, в первую очередь, от наследования от streambuf. Проконсультируйтесь с вашим ближайшим экспертом по C ++ iostream, чтобы узнать, каков правильный правильный путь. Но, надеюсь, это доказательство того, что что-то подобное возможно.

...