c++ c++11 move-semantics ostringstream

c++ - Mueve la cadena fuera de un std:: ostringstream



c++11 move-semantics (4)

Si construyo una cadena hecha de una lista de valores de punto flotante separados por espacios usando std::ostringstream :

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

Entonces obtengo el resultado en un std::string :

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

Sin embargo, esto causará una copia profunda innecesaria del contenido de la cadena, ya que no se utilizará más ss .

¿Hay alguna forma de construir la cadena sin copiar todo el contenido?


+1 para Boost Karma por @Cubbi y la sugerencia de "crear tu propio tipo streambuf -dervied que no haga una copia, y dárselo al constructor de un basic_istream<> ". .

Sin embargo, falta una respuesta más genérica, y se encuentra entre estos dos. Utiliza Boost Iostreams:

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

Aquí hay un programa de demostración:

Vivir en 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; }

Tenga en cuenta que puede reemplazar trivialmente la std::string con std::wstring , o std::vector<char> etc.

Aún mejor, puede usarlo con el dispositivo array_sink y tener un búfer de tamaño fijo . De esa manera, puede evitar cualquier asignación de búfer con su código Iostreams.

Vivir en 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; }

Ambos programas se imprimen:

Output contains: Hello world 0x2a true


Actualización: En vista de que a la gente no le gusta esta respuesta, pensé que haría una edición y una explicación.

  1. No, no hay forma de evitar una copia de cadena (stringbuf tiene la misma interfaz)

  2. Nunca importará. En realidad es más eficiente de esa manera. (Intentaré explicar esto)

Imagine que escribe una versión de stringbuf que mantiene una std::string disponible y perfecta en todo momento. (En realidad he intentado esto).

Añadir caracteres es fácil: simplemente usamos push_back en la cadena subyacente.

Está bien, pero ¿qué pasa con la eliminación de caracteres (lectura del búfer)? Tendremos que mover algún puntero para tener en cuenta los caracteres que hemos eliminado, todo bien y bien.

Sin embargo, tenemos un problema: el contrato que mantenemos dice que siempre tendremos disponible una std::string .

Por lo tanto, cada vez que eliminemos caracteres de la secuencia, tendremos que erase de la cadena subyacente. Eso significa barajar todos los caracteres restantes ( memmove / memcpy ). Debido a que este contrato debe mantenerse cada vez que el flujo de control abandona nuestra implementación privada, en la práctica, esto significa tener que borrar caracteres de la cadena cada vez que llamamos getc o se gets en el buffer de cadena. Esto se traduce en una llamada a borrar en cada operación << en el flujo.

Luego, por supuesto, está el problema de implementar el búfer pushback. Si inserta caracteres en la cadena subyacente, debe insert en la posición 0, barajando todo el búfer.

En resumen, puede escribir un búfer de flujo solo para ostream solo para crear una std::string . Aún tendrás que lidiar con todas las reasignaciones a medida que crece el búfer subyacente, por lo que al final puedes guardar exactamente una copia de cadena. Así que tal vez pasemos de 4 copias de cadenas (y llamadas a malloc / free) a 3, o 3 a 2.

También deberá lidiar con el problema de que la interfaz de streambuf no se divide en istreambuf y ostreambuf . Esto significa que todavía tiene que ofrecer la interfaz de entrada y lanzar excepciones o afirmar si alguien lo usa. Esto equivale a mentir a los usuarios: no hemos implementado una interfaz esperada.

Para esta pequeña mejora en el rendimiento, debemos pagar el costo de:

  1. desarrollo de un componente de software (bastante complejo, cuando se toma en cuenta la administración del entorno local)

  2. sufriendo la pérdida de flexibilidad de tener un streambuf que solo admite operaciones de salida.

  3. Colocación de minas terrestres para futuros desarrolladores para pisar.


Implementé la clase "outstringstream", que creo que hace exactamente lo que necesitas (ver el método take_str ()). Usé parcialmente el código de: ¿Qué hay de malo en mi implementación de overflow ()?

#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>;


std::ostringstream no ofrece una interfaz pública para acceder a su búfer en memoria a menos que no sea portabosamente compatible con pubsetbuf (pero incluso entonces su búfer es de tamaño fijo, vea el ejemplo de cppreference )

Si desea torturar algunas secuencias de cadenas, puede acceder al búfer utilizando la interfaz protegida:

#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''; }

La forma estándar de C ++ de acceder directamente a un búfer de flujo de salida de tamaño automático es ofrecida por std::ostrstream , obsoleta en C ++ 98, pero aún estándar C ++ 14 y contando.

#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 }

Sin embargo, creo que la solución más limpia (y la más rápida) es 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''s your string }