Подтвердить что ты не робот

Переместите строку из std:: ostringstream

Если я построю строку, состоящую из списка значений с плавающей запятой, разделенных пробелами, используя std::ostringstream:

std::ostringstream ss;
unsigned int s = floatData.size();
for(unsigned int i=0;i<s;i++)
{
    ss << floatData[i] << " ";
}

Затем я получаю результат в std::string:

std::string textValues(ss.str());

Однако это приведет к ненужной глубокой копии содержимого строки, поскольку ss больше не будет использоваться.

Есть ли способ построить строку без копирования всего содержимого?

4b9b3361

Ответ 1

std::ostringstream не предлагает публичного интерфейса для доступа к его буферу в памяти, если он не поддерживает переносимость pubsetbuf (но даже тогда ваш буфер имеет фиксированный размер, см. пример cppreference)

Если вы хотите пытать некоторые строковые потоки, вы можете получить доступ к буфере с помощью защищенного интерфейса:

#include <iostream>
#include <sstream>
#include <vector>

struct my_stringbuf : std::stringbuf {
    const char* my_str() const { return pbase(); } // pptr might be useful too
};

int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    my_stringbuf buf;
    std::ostream ss(&buf);
    for(unsigned int i=0; i < v.size(); ++i)
        ss << v[i] << ' ';
    ss << std::ends;
    std::cout << buf.my_str() << '\n';
}

Стандартный способ С++ прямого доступа к буфере выходного потока с автоматическим изменением размера предлагается std::ostrstream, устаревшим на С++ 98, но все же стандартным С++ 14 и подсчетом.

#include <iostream>
#include <strstream>
#include <vector>

int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    std::ostrstream ss;
    for(unsigned int i=0; i < v.size(); ++i)
        ss << v[i] << ' ';
    ss << std::ends;
    const char* buffer = ss.str(); // direct access!
    std::cout << buffer << '\n';
    ss.freeze(false); // abomination
}

Однако я считаю, что самое чистое (и самое быстрое) решение boost.karma

#include <iostream>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
int main()
{
    std::vector<float> v = {1.1, -3.4, 1/7.0};
    std::string s;
    karma::generate(back_inserter(s), karma::double_ % ' ', v);
    std::cout << s << '\n'; // here your string
}

Ответ 2

Я реализовал класс outstringstream, который, как я считаю, делает именно то, что вам нужно (см. метод take_str()). Я частично использовал код из: Что не так с моей реализацией переполнения()?

#include <ostream>

template <typename char_type>
class basic_outstringstream : private std::basic_streambuf<char_type, std::char_traits<char_type>>,
                              public std::basic_ostream<char_type, std::char_traits<char_type>>
{
    using traits_type = std::char_traits<char_type>;
    using base_buf_type = std::basic_streambuf<char_type, traits_type>;
    using base_stream_type = std::basic_ostream<char_type, traits_type>;
    using int_type = typename base_buf_type::int_type;

    std::basic_string<char_type> m_str;

    int_type overflow(int_type ch) override
    {
        if (traits_type::eq_int_type(ch, traits_type::eof()))
            return traits_type::not_eof(ch);

        if (m_str.empty())
            m_str.resize(1);
        else
            m_str.resize(m_str.size() * 2);

        const std::ptrdiff_t diff = this->pptr() - this->pbase();
        this->setp(&m_str.front(), &m_str.back());

        this->pbump(diff);
        *this->pptr() = traits_type::to_char_type(ch);
        this->pbump(1);

        return traits_type::not_eof(traits_type::to_int_type(*this->pptr()));
    }

    void init()
    {
        this->setp(&m_str.front(), &m_str.back());

        const std::size_t size = m_str.size();
        if (size)
        {
            memcpy(this->pptr(), &m_str.front(), size);
            this->pbump(size);
        }
    }

public:

    explicit basic_outstringstream(std::size_t reserveSize = 8)
        : base_stream_type(this)
    {
        m_str.reserve(reserveSize);
        init();
    }

    explicit basic_outstringstream(std::basic_string<char_type>&& str)
        : base_stream_type(this), m_str(std::move(str))
    {
        init();
    }

    explicit basic_outstringstream(const std::basic_string<char_type>& str)
        : base_stream_type(this), m_str(str)
    {
        init();
    }

    const std::basic_string<char_type>& str() const
    {
        return m_str;
    }

    std::basic_string<char_type>&& take_str()
    {
        return std::move(m_str);
    }

    void clear()
    {
        m_str.clear();
        init();
    }
};

using outstringstream = basic_outstringstream<char>;
using woutstringstream = basic_outstringstream<wchar_t>;

Ответ 3

+1 для Boost Karma by @Cubbi и предложение " создать собственный streambuf -услуженный тип, который не создает копию, и дать это конструктору a basic_istream<>..

Более общий ответ, однако, отсутствует и находится между этими двумя. Он использует Boost Iostreams:

using string_buf = bio::stream_buffer<bio::back_insert_device<std::string> >;

Вот демо-программа:

Live On Coliru

#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream_buffer.hpp>

namespace bio = boost::iostreams;

using string_buf = bio::stream_buffer<bio::back_insert_device<std::string> >;

// any code that uses ostream
void foo(std::ostream& os) {
    os << "Hello world " 
       << std::hex << std::showbase << 42
       << " " << std::boolalpha << (1==1) << "\n";
}

#include <iostream>

int main() {
    std::string output;
    output.reserve(100); // optionally optimize if you know roughly how large output is gonna, or know what minimal size it will require

    {
        string_buf buf(output);
        std::ostream os(&buf);
        foo(os);
    }

    std::cout << "Output contains: " << output;
}

Обратите внимание, что вы можете тривиально заменить std::string на std::wstring или std::vector<char> и т.д.

Еще лучше, вы можете использовать его с устройством array_sink и иметь буфер фиксированного размера. Таким образом, вы можете избежать любого распределения буфера с помощью кода Iostreams!

Live On Coliru

#include <boost/iostreams/device/array.hpp>

using array_buf = bio::stream_buffer<bio::basic_array<char>>;

// ...

int main() {
    char output[100] = {0};

    {
        array_buf buf(output);
        std::ostream os(&buf);
        foo(os);
    }

    std::cout << "Output contains: " << output;
}

Обе программы печатают:

Output contains: Hello world 0x2a true

Ответ 4

Обновление: перед лицом людей, которые не любили этот ответ, я думал, что сделаю редактирование и объясню.

  • Нет, нет способа избежать строковой копии (stringbuf имеет тот же интерфейс)

  • Это никогда не имеет значения. Это действительно более эффективно. (Я попытаюсь это объяснить)

Представьте, что вы пишете версию stringbuf, которая всегда поддерживает идеальный, подвижный std::string. (Я действительно пробовал это).

Добавление символов легко - мы просто используем push_back в базовой строке.

ОК, но как насчет удаления символов (чтение из буфера)? Нам придется переместить некоторый указатель на учетную запись для символов, которые мы удалили, все хорошо и хорошо.

Однако у нас есть проблема - контракт, который мы поддерживаем, говорит, что у нас всегда будет доступный std::string.

Поэтому всякий раз, когда мы удаляем символы из потока, нам нужно erase их из базовой строки. Это означает перетасовку остальных оставшихся символов (memmove/memcpy). Поскольку этот контракт должен храниться каждый раз, когда поток управления выходит из нашей частной реализации, это на практике означает необходимость стирать символы из строки каждый раз, когда мы вызываем getc или gets в буфере строк. Это переводит на вызов стирания при каждой операции << в потоке.

Тогда, конечно, проблема реализации буфера pushback. Если вы проталкиваете символы в базовую строку, вы попадете в insert их в позиции 0 - перетасовываете весь буфер вверх.

Достаточно долгое, что вы можете написать буфер потока только для потока только для построения std::string. Вам все равно придется иметь дело со всеми перераспределениями по мере роста базового буфера, поэтому в итоге вы получите только одну копию строки. Поэтому, возможно, мы переходим от 4 строковых копий (и звонков в malloc/free) до 3 или от 3 до 2.

Вам также понадобится решить проблему, когда интерфейс streambuf не разбит на istreambuf и ostreambuf. Это означает, что вы все еще должны предлагать интерфейс ввода и либо бросать исключения, либо утверждать, если кто-то использует его. Это сводится к лжи для пользователей - нам не удалось реализовать ожидаемый интерфейс.

Для этого небольшого улучшения производительности мы должны оплатить стоимость:

  • разработка (довольно сложная, когда вы учитываете локальное управление) компонент программного обеспечения.

  • терпит потерю гибкости наличия streambuf, который поддерживает только выходные операции.

  • Укладка противопехотных мин для будущих разработчиков.

Ответ 5

Я исправил очень хороший ответ @Kuba answer, чтобы исправить некоторые проблемы (к сожалению, в настоящее время он не отвечает). В частности:

  • добавлен safe_pbump для обработки 64-битных смещений;
  • вернуть string_view вместо string (внутренняя строка не имеет правильного размера буфера);
  • измените размер string до текущего размера буфера в методе семантики перемещения take_str;
  • исправлена семантика перемещения метода take_str с помощью init перед возвратом;
  • удален бесполезный метод memcpy для init;
  • переименовал параметр шаблона char_type в CharT, чтобы избежать неоднозначности с basic_streambuf::char_type;
  • использовал string::data() и арифметику указателя вместо возможного неопределенного поведения, используя string::front() и string::back(), как указано @LightnessRacesinOrbit;
  • Реализация с композицией streambuf.
#pragma once

#include <cstdlib>
#include <limits>
#include <ostream>
#include <string>
#if __cplusplus >= 201703L
#include <string_view>
#endif

namespace usr
{
    template <typename CharT>
    class basic_outstringstream : public std::basic_ostream<CharT, std::char_traits<CharT>>
    {
        using traits_type = std::char_traits<CharT>;
        using base_stream_type = std::basic_ostream<CharT, traits_type>;

        class buffer : public std::basic_streambuf<CharT, std::char_traits<CharT>>
        {
            using base_buf_type = std::basic_streambuf<CharT, traits_type>;
            using int_type = typename base_buf_type::int_type;

        private:
            size_t size() const
            {
                return (size_t)(this->pptr() - this->pbase());
            }

            void safe_pbump(std::streamsize off)
            {
                // pbump does not support 64 bit offsets
                // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47921
                int maxbump;
                if (off > 0)
                    maxbump = std::numeric_limits<int>::max();
                else if (off < 0)
                    maxbump = std::numeric_limits<int>::min();
                else // == 0
                    return;

                while (std::abs(off) > std::numeric_limits<int>::max())
                {
                    this->pbump(maxbump);
                    off -= maxbump;
                }

                this->pbump((int)off);
            }

            void init()
            {
                this->setp(const_cast<CharT *>(m_str.data()),
                    const_cast<CharT *>(m_str.data()) + m_str.size());
                this->safe_pbump((streamsize)m_str.size());
            }

        protected:
            int_type overflow(int_type ch) override
            {
                if (traits_type::eq_int_type(ch, traits_type::eof()))
                    return traits_type::not_eof(ch);

                if (m_str.empty())
                    m_str.resize(1);
                else
                    m_str.resize(m_str.size() * 2);

                size_t size = this->size();
                this->setp(const_cast<CharT *>(m_str.data()),
                    const_cast<CharT *>(m_str.data()) + m_str.size());
                this->safe_pbump((streamsize)size);
                *this->pptr() = traits_type::to_char_type(ch);
                this->pbump(1);

                return ch;
            }

        public:
            buffer(std::size_t reserveSize)
            {
                m_str.reserve(reserveSize);
                init();
            }

            buffer(std::basic_string<CharT>&& str)
                : m_str(std::move(str))
            {
                init();
            }

            buffer(const std::basic_string<CharT>& str)
                : m_str(str)
            {
                init();
            }

        public:
#if __cplusplus >= 201703L
            std::basic_string_view<CharT> str() const
            {
                return std::basic_string_view<CharT>(m_str.data(), size());
            }
#endif
            std::basic_string<CharT> take_str()
            {
                // Resize the string to actual used buffer size
                m_str.resize(size());
                string ret = std::move(m_str);
                init();
                return ret;
            }

            void clear()
            {
                m_str.clear();
                init();
            }

        private:
            std::basic_string<CharT> m_str;
        };

    public:
        explicit basic_outstringstream(std::size_t reserveSize = 8)
            : base_stream_type(nullptr), m_buffer(reserveSize)
        {
            this->rdbuf(&m_buffer);
        }

        explicit basic_outstringstream(std::basic_string<CharT>&& str)
            : base_stream_type(nullptr), m_buffer(str)
        {
            this->rdbuf(&m_buffer);
        }

        explicit basic_outstringstream(const std::basic_string<CharT>& str)
            : base_stream_type(nullptr), m_buffer(str)
        {
            this->rdbuf(&m_buffer);
        }

#if __cplusplus >= 201703L
        std::basic_string_view<CharT> str() const
        {
            return m_buffer.str();
        }
#endif
        std::basic_string<CharT> take_str()
        {
            return m_buffer.take_str();
        }

        void clear()
        {
            m_buffer.clear();
        }

        size_t size() const
        {
            return  m_buffer.size();
        }

    private:
        buffer m_buffer;
    };

    using outstringstream = basic_outstringstream<char>;
    using woutstringstream = basic_outstringstream<wchar_t>;
}