txt pseudocodigo manejo libreria lenguaje leer hacer guardar flujo ejemplos diagramas diagrama datos convertir como codificacion archivos archivo c++ iostream

pseudocodigo - Cómo escribir un flujo de entrada personalizado en C++



manejo de archivos en c++ fstream (5)

Estoy de acuerdo con @DeadMG y no recomendaría el uso de iostreams. Aparte del diseño deficiente, el rendimiento es a menudo peor que el de la E / S de estilo C simple y antigua. Sin embargo, no me quedaría con una biblioteca de E / S en particular, sino que crearía una interfaz (clase abstracta) que tiene todas las operaciones requeridas, por ejemplo:

class Input { public: virtual void read(char *buffer, size_t size) = 0; // ... };

Luego puede implementar esta interfaz para CI / O, iostreams, mmap o lo que sea.

Actualmente estoy aprendiendo C ++ (proveniente de Java) y estoy tratando de entender cómo usar las secuencias de IO correctamente en C ++.

Digamos que tengo una clase de Image que contiene los píxeles de una imagen y sobrecargué al operador de extracción para leer la imagen de una secuencia:

istream& operator>>(istream& stream, Image& image) { // Read the image data from the stream into the image return stream; }

Así que ahora puedo leer una imagen como esta:

Image image; ifstream file("somepic.img"); file >> image;

Pero ahora quiero usar el mismo operador de extracción para leer los datos de imagen de una secuencia personalizada. Digamos que tengo un archivo que contiene la imagen en forma comprimida. Entonces, en lugar de usar ifstream, es posible que desee implementar mi propio flujo de entrada. Al menos así lo haría en Java. En Java escribiría una clase personalizada extendiendo la clase InputStream e implementando el método int read() . Así que eso es bastante fácil. Y el uso se vería así:

InputStream stream = new CompressedInputStream(new FileInputStream("somepic.imgz")); image.read(stream);

Entonces usando el mismo patrón tal vez quiero hacer esto en C ++:

Image image; ifstream file("somepic.imgz"); compressed_stream stream(file); stream >> image;

Pero tal vez sea el camino equivocado, no lo sé. La extensión de la clase istream parece bastante complicada y, después de algunas búsquedas, encontré algunos consejos sobre la extensión de streambuf . Pero este example parece terriblemente complicado para una tarea tan simple.

Entonces, ¿cuál es la mejor manera de implementar flujos de entrada / salida personalizados (o streambufs?) En C ++?

Solución

Algunas personas sugirieron no usar iostreams en absoluto y usar iteradores, boost o una interfaz IO personalizada en su lugar. Estas pueden ser alternativas válidas pero mi pregunta fue sobre iostreams. La respuesta aceptada dio como resultado el código de ejemplo a continuación. Para facilitar la lectura, no hay separación de encabezado / código y se importa todo el espacio de nombres estándar (sé que esto es algo malo en el código real).

Este ejemplo se trata de leer y escribir imágenes codificadas en xor vertical. El formato es bastante sencillo. Cada byte representa dos píxeles (4 bits por píxel). Cada línea está xoreada con la línea anterior. Este tipo de codificación prepara la imagen para la compresión (generalmente resulta en un lote de 0 bytes que son más fáciles de comprimir).

#include <cstring> #include <fstream> using namespace std; /*** vxor_streambuf class ******************************************/ class vxor_streambuf: public streambuf { public: vxor_streambuf(streambuf *buffer, const int width) : buffer(buffer), size(width / 2) { previous_line = new char[size]; memset(previous_line, 0, size); current_line = new char[size]; setg(0, 0, 0); setp(current_line, current_line + size); } virtual ~vxor_streambuf() { sync(); delete[] previous_line; delete[] current_line; } virtual streambuf::int_type underflow() { // Read line from original buffer streamsize read = buffer->sgetn(current_line, size); if (!read) return traits_type::eof(); // Do vertical XOR decoding for (int i = 0; i < size; i += 1) { current_line[i] ^= previous_line[i]; previous_line[i] = current_line[i]; } setg(current_line, current_line, current_line + read); return traits_type::to_int_type(*gptr()); } virtual streambuf::int_type overflow(streambuf::int_type value) { int write = pptr() - pbase(); if (write) { // Do vertical XOR encoding for (int i = 0; i < size; i += 1) { char tmp = current_line[i]; current_line[i] ^= previous_line[i]; previous_line[i] = tmp; } // Write line to original buffer streamsize written = buffer->sputn(current_line, write); if (written != write) return traits_type::eof(); } setp(current_line, current_line + size); if (!traits_type::eq_int_type(value, traits_type::eof())) sputc(value); return traits_type::not_eof(value); }; virtual int sync() { streambuf::int_type result = this->overflow(traits_type::eof()); buffer->pubsync(); return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0; } private: streambuf *buffer; int size; char *previous_line; char *current_line; }; /*** vxor_istream class ********************************************/ class vxor_istream: public istream { public: vxor_istream(istream &stream, const int width) : istream(new vxor_streambuf(stream.rdbuf(), width)) {} virtual ~vxor_istream() { delete rdbuf(); } }; /*** vxor_ostream class ********************************************/ class vxor_ostream: public ostream { public: vxor_ostream(ostream &stream, const int width) : ostream(new vxor_streambuf(stream.rdbuf(), width)) {} virtual ~vxor_ostream() { delete rdbuf(); } }; /*** Test main method **********************************************/ int main() { // Read data ifstream infile("test.img"); vxor_istream in(infile, 288); char data[144 * 128]; in.read(data, 144 * 128); infile.close(); // Write data ofstream outfile("test2.img"); vxor_ostream out(outfile, 288); out.write(data, 144 * 128); out.flush(); outfile.close(); return 0; }


La forma correcta de crear una nueva secuencia en C ++ es derivar de std::streambuf y anular la operación underflow() para leer y las operaciones overflow() y sync() para escribir. Para su propósito, crearía un búfer de flujo de filtrado que toma otro búfer de flujo (y posiblemente un flujo del que se puede extraer el búfer de flujo utilizando rdbuf() ) como argumento e implementa sus propias operaciones en términos de este búfer de flujo.

El esquema básico de un buffer de flujo sería algo como esto:

class compressbuf : public std::streambuf { std::streambuf* sbuf_; char* buffer_; // context for the compression public: compressbuf(std::streambuf* sbuf) : sbuf_(sbuf), buffer_(new char[1024]) { // initialize compression context } ~compressbuf() { delete[] this->buffer_; } int underflow() { if (this->gptr() == this->egptr()) { // decompress data into buffer_, obtaining its own input from // this->sbuf_; if necessary resize buffer // the next statement assumes "size" characters were produced (if // no more characters are available, size == 0. this->setg(this->buffer_, this->buffer_, this->buffer_ + size); } return this->gptr() == this->egptr() ? std::char_traits<char>::eof() : std::char_traits<char>::to_int_type(*this->gptr()); } };

La apariencia exacta de underflow() depende de la biblioteca de compresión utilizada. La mayoría de las bibliotecas que he usado mantienen un búfer interno que debe llenarse y que retiene los bytes que aún no se consumen. Típicamente, es bastante fácil enganchar la descompresión en el underflow() .

Una vez que se crea el búfer de flujo, puede simplemente inicializar un objeto std::istream con el búfer de flujo:

std::ifstream fin("some.file"); compressbuf sbuf(fin.rdbuf()); std::istream in(&sbuf);

Si va a utilizar el búfer de flujo con frecuencia, es posible que desee encapsular la construcción del objeto en una clase, por ejemplo, icompressstream . Hacerlo es un poco complicado porque la clase base std::ios es una base virtual y es la ubicación real donde se almacena el búfer de flujo. Para construir el búfer de flujo antes de pasar un puntero a un std::ios , se requiere saltar a través de unos pocos aros: requiere el uso de una clase base virtual . Aquí es cómo esto podría verse más o menos:

struct compressstream_base { compressbuf sbuf_; compressstream_base(std::streambuf* sbuf): sbuf_(sbuf) {} }; class icompressstream : virtual compressstream_base , public std::istream { public: icompressstream(std::streambuf* sbuf) : compressstream_base(sbuf) , std::ios(&this->sbuf_) , std::istream(&this->sbuf_) { } };

(Acabo de escribir este código sin una forma sencilla de probar que es razonablemente correcto; espere errores tipográficos, pero el enfoque general debería funcionar como se describe)


No lo hagas, a menos que quieras morir una muerte terrible de diseño horrible. Los IOstreams son el peor componente de la biblioteca estándar, incluso peor que los locales. El modelo de iterador es mucho más útil, y puede convertir de secuencia a iterador con istream_iterator.


Probablemente sea posible hacer esto, pero creo que no es el uso "correcto" de esta función en C ++. Los operadores iostream >> y << están diseñados para operaciones bastante simples, como escribir el "nombre, calle, ciudad, código postal" de una class Person , no para analizar y cargar imágenes. Eso se hace mucho mejor usando stream :: read () - usando Image(astream); , y puede implementar un flujo para la compresión, tal como lo describe Dietmar.


boost (que ya debería tener si está interesado en C ++), tiene una biblioteca completa dedicada a extender y personalizar flujos de IO: boost.iostreams

En particular, ya tiene flujos de descompresión para algunos formatos populares ( bzip2 , gzlib y zlib )

Como viste, extender streambuf puede ser un trabajo envolvente, pero la biblioteca hace que sea bastante fácil escribir tu propio streambuf de filtrado si lo necesitas.