Comment écrire personnalisé flux d'entrée en C++

Je suis présentement en train d'apprendre le C++ (en Venant de Java) et j'essaie de comprendre comment utiliser IO flux correctement en C++.

Disons que j'ai un Image classe qui contient les pixels d'une image et j'ai surchargé l'opérateur d'extraction lecture de l'image à partir d'un flux:

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

Alors maintenant, je suis capable de lire une image comme ceci:

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

Mais maintenant je veux utiliser le même opérateur d'extraction de lire les données d'image à partir d'un flux personnalisé. Disons que j'ai un fichier qui contient l'image sous forme compressée. Donc, au lieu d'utiliser ifstream que je veuille mettre en place mes propres flux d'entrée. Au moins, c'est comment j'allais le faire en Java. En Java, je voudrais écrire une classe personnalisée extension de la InputStream classe et la mise en œuvre de la int read() méthode. Donc c'est assez facile. Et l'utilisation ressemblerait à ceci:

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

Donc, en utilisant le même modèle peut-être que je veux le faire en C++:

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

Mais c'est peut-être le mauvais sens, je ne sais pas. L'extension de la istream classe a l'air assez compliqué et après quelques recherches j'ai trouvé quelques conseils sur l'extension de streambuf à la place. Mais ce exemple semble terriblement compliqué pour une telle tâche simple.

Quelle est donc la meilleure façon de mettre en œuvre personnalisée d'entrée/sortie flux (ou streambufs?) en C++?

Solution

Certaines personnes ont suggéré de ne pas utiliser iostreams à tous et à utiliser des itérateurs, boost ou une coutume IO interface à la place. Ceux-ci peuvent être des alternatives valables, mais ma question était sur le iostreams. La accepté de répondre a entraîné dans l'exemple de code ci-dessous. Pour faciliter la lecture il n'y a pas d'en-tête/séparation du code et de l'ensemble de l'espace de noms std est importé (je sais que c'est une mauvaise chose dans le code réel).

Cet exemple est à propos de la lecture et de l'écriture verticale-xor des images codées. Le format est assez facile. Chaque octet représente deux pixels (4 bits par pixel). Chaque ligne est avez xor avec la ligne précédente. Ce type de codage prépare l'image pour la compression (généralement les résultats dans beaucoup de 0 octets qui sont plus faciles à compresser).

#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;
}
  • Je recommande fortement d'éviter iostreams. Voir stackoverflow.com/questions/2753060/... , accu.org/index.php/journals/1539 et google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams pour apprendre certaines des raisons pour lesquelles.
  • Si j'ai bien compris le Google guide de style correctement puis ils vous recommandons d'utiliser l'ancien style C I/O des trucs? Mais je ne vois pas comment je peux abstrait I/O à l'écart de mes cours alors. Mon Image de la classe veut juste lire les données et il ne veut pas se soucier de la source de données ou si la source de données est compressés ou cryptés ou quoi que ce soit. Avec de vieux style C I/O je peux passer d'un descripteur de fichier, mais c'est tout. Ne sonne pas comme une bonne alternative.
  • Comme suggéré par DeadMG, vous pouvez travailler avec des itérateurs à la place. Ou vous pouvez créer une interface simple (classe abstraite) qui définit les quelques opérations que vous avez besoin, comme read() que vous avez mentionnés. Ensuite, vous pouvez avoir plusieurs implémentations de l'interface, par exemple à l'aide de C-style I/O, ou mmap ou que ce soit, même iostreams.
  • Question: Souhaitez-vous transmettre dans un flux standard comme std::cout dans le streambuf argument du constructeur?
InformationsquelleAutor kayahr | 2012-12-29