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?
Vous devez vous connecter pour publier un commentaire.
La bonne façon de créer un nouveau flux de données en C++ est de dériver de
std::streambuf
et de remplacer launderflow()
de fonctionnement pour la lecture et laoverflow()
etsync()
opérations pour l'écriture. Pour votre but, vous devez créer un système de filtrage de la mémoire tampon qui prend une autre mémoire tampon du flux (et éventuellement un flux de données à partir de laquelle le flux de la mémoire tampon peut être extraite à l'aide derdbuf()
) comme argument et met en œuvre ses propres opérations en termes de ce mémoire tampon du flux.Le schéma de base d'une mémoire tampon du flux serait quelque chose comme ceci:
Comment
underflow()
ressemble exactement dépend de la bibliothèque de compression utilisé. La plupart des bibliothèques, j'ai utilisé de garder une mémoire tampon interne qui doit être rempli et qui conserve les octets qui ne sont pas encore consommé. Généralement, il est assez facile de relier la décompression enunderflow()
.Une fois la mémoire tampon du flux est créé, vous pouvez initialiser un
std::istream
objet avec la mémoire tampon du flux:Si vous allez utiliser la mémoire tampon du flux fréquemment, vous pouvez encapsuler la construction de l'objet dans une classe, par exemple,
icompressstream
. C'est un peu délicat parce que la classe de basestd::ios
est une base virtuel et l'actuel emplacement de la mémoire tampon du flux est stocké. Pour construire la mémoire tampon du flux avant de passer un pointeur vers unstd::ios
nécessite donc de sauter à travers quelques cerceaux: Il nécessite l'utilisation d'unvirtual
de la classe de base. Voici comment cela pourrait-il l'air à peu près:(J'ai juste tapé ce code sans un moyen simple pour tester qu'il est raisonnablement correcte; veuillez attendre les fautes de frappe, mais l'ensemble de la démarche doit fonctionner comme décrit)
istream
de la lecture.istream
ne fait pas d'entrée physique; il délègue cette tâche à unestreambuf
, en utilisant le modèle de stratégie. Le constructeur deistream
prend unstreambuf*
comme argument. Dans le classiqueistream
(ifstream
etistringstream
), cet argument est fourni par la classe dérivée, mais il n'y a rien pour vous empêcher de l'instanciation d'unistream
directement, avec un pointeur vers unestreambuf
que vous fournissez, ou de tirer deistream
de sorte que la classe dérivée constructeur peut fournir unstreambuf
du type désiré.InputStream
est plus proche destd::streambuf
que c'est àstd::istream
.std::istream
est plus de JavaFormat
, mais avec une interface qui le rend beaucoup plus simple à utiliser.cout << std::setiosflags(std::ios::right) << std::setw(12) << s
, pour ne pas mentionner que iostreams sont terribles pour l'i18n.std::istream
et créé mon custom de la mémoire tampon dans le constructeur et l'a transmis à lainit
méthode deistream
et il fonctionne comme un charme. Il n'y a rien de mauvais dans cette approche?Format
classe pour chaque type nouveau, puis instancier chacun lors de la sortie de différents types. Il vous suffit d'utiliser<<
, et tout fonctionne. Et bien sûr, personne dans leur bon esprit jamais écritstd::setiosflags(whatever)
. Vous définissez personnalisé manipulateurs logique de balisage. Vous définir en un seul endroit ce que par exemple un taux d'intérêt devrait ressembler, et ensuite, vous écrivezstd::cout << interestRate << value;
. (Pour autant que je sais, C++ et Java offrent cette possibilité, mais la version de Java est beaucoup plus difficile à utiliser.)Format
classe n'est pas une caractéristique inhérente à un formatage de la bibliothèque, vous pouvez facilement le faire sans que même faciliter l'héritage iostream deoperator<<
. Je suis d'accord que vous pouvez répondre à certaines des questions de mise en forme avec des manipulateurs, mais pas la conception des défauts tels que le mixage de mise en forme avec I/O. Aussi, je ne vois pas comment vous pouvez résoudre les problèmes avec i18n sans l'aide d'une mise en forme appropriée de la bibliothèque.init()
est un peu gênant. La problématique de l'opération est en fait la destruction: Bien qu'il n'a pas d'importance pour les flux d'entrée, il est d'une importance cruciale pourstd::ostream
la mémoire tampon est vidé par le destructeur destd::ostream
. Si la mémoire tampon du flux n'est pas la première base virtuelle, il sera déjà été détruits à l'époque.streambuf
; la mise en forme à l'aide de la<<
opérateur surostream
. Deux classes différentes, avec complètement différentes préoccupations. (En ce qui concerne la qualité de l'I18n, ni Java et C++ ont une solution. En gros, vous avez à écrire une DLL/classe pour chaque endroit. Le langage humain est tout de même de plusieurs ordres de grandeur plus complexe que les langages de programmation.)"Cannot open: " << filename
, et au moins dans les langues que je connais, vous pouvez utiliser d'autres constructions similaires, par exemple"Datei kann nicht geöffnet werden: " << filename
. Les résultats ne peuvent pas être idiomatiques, mais ils sont acceptables.streambuf
, vous verrez que c'est un gâchis d'entrée, de sortie, de tampon et de même locale (!) avec un horrible API, il est donc plus qu'un détail d'implémentation de iostreams plutôt qu'à une entité distincte.streambuf
pourrait certainement être mieux, et j'aimerais aussi voir la traduction de code séparé du reste (mais parce que le C++ prend en charge bidirectionnelle des cours d'eau avec la recherche, c'est pas pratique). D'autre part,streambuf
est certainement une catégorie distincte de la hiérarchie de iostream.iostream
l'utilise, mais vous tirer séparément, pour personnaliser l'évier et de la source, où que vous ajoutez un<<
et un>>
de formater une nouvelle classe. La fonctionnalité est exactement la même que celle de Java, mais c'est un ordre de grandeur plus facile pour le client, le code à utiliser.this->flush()
qui à son tour appellethis->rdbuf()->pubsync()
accès à la mémoire tampon du flux. Évidemment, à ce stade de la mémoire tampon du flux ne doit pas être détruit. Si votre flux de tampon est une simple classe de base, unvirtual
de la classe de base suivantesstd::ostream
, ou un membre de la rivière, il sera détruit au point dans le temps lorsque c'est l'accès. Il peut sembler à travailler parce que votre mémoire tampon du flux probablement ne change pas vraiment sa représentation dans le destructeur, mais il est strictement comportement indéfini.pubsync()
est appelée sur votre mémoire tampon du flux, il est délégué à lavirtual
fonctionsync()
qui vous pouvez remplacer pour rincer votre propre tampon et ensuite appelerthis->sbuf_->pubsync()
sur le flux sous-jacent bufffer. Depuisstd::ostream::~ostream()
appelsthis->flush()
qui appellethis->rdbuf()->pubsync()
(si le flux est engood()
état) le flux de tampons devraient automatiquement être vidées.flush()
de le destructeur destd::ostream::~ostream()
. En fait, c'est une remarque indiquant que le destructeur n'a pas accèsrdbuf()
(27.7.3.2 [ostream.contre] le paragraphe 4). Je pense que c'est un changement par rapport à C++ 2003, mais actuellement je ne peut pas facilement de recherche, la liste des problèmes. Un autre peu d'information que j'ai appris. En ce qui concerne non à l'aide de IOStreams: je pense que ces peopel sont mal... 😉gettext
, mais je n'ai jamais été en mesure de le faire gérer les choses comme le sexe (nécessaire en français et en allemand, mais avec des règles différentes), double (necessart sapin arabe), et un certain nombre d'autres traits grammaticaux. Réaliste ou pas, vous ne pouvez pas produire idiomatiques texte locaux autres que par l'utilisation d'une DLL différent pour chaque localité.boost (que vous devriez déjà avoir si vous êtes sérieux au sujet de C++), qui présente une bibliothèque entière consacrée à l'extension et à la personnalisation de IO flux: coup de pouce.iostreams
En particulier, il a déjà la décompression des flux pour quelques formats populaires (bzip2, gzlib, et zlib)
Comme vous l'avez vu, l'extension de streambuf peut être un impliquant de l'emploi, mais la bibliothèque le rend assez facile à écrivez votre propre filtrage streambuf si vous en avez besoin.
Ne le font pas, sauf si vous voulez mourir d'une mort terrible de hideux de la conception. IOstreams sont les pires composant de la bibliothèque Standard - encore pire que les paramètres régionaux. L'itérateur modèle est beaucoup plus utile, et vous pouvez convertir les flux d'itérateur, istream_iterator.
iostream
est probablement le meilleur conçus partie de la bibliothèque standard, bien qu'il souffre d'une mauvaise choisi de noms dans certains cas. À la différence de l'ei dans la plupart des autres langues, il parvient à garder les concepts majeurs (mise en forme/analyse du vs sinking/sourcing caractères) bien séparés, et permet de presque illimitées de personnalisation de chacun. (Java a la même séparation, mais ils ont réussi à le rendre beaucoup plus compliqué mise en forme pour les types définis par l'utilisateur, et beaucoup plus difficile à utiliser). Et pour autant que je sais, C++ est la seule langue dont IO soutient la logique de balisage.Je suis d'accord avec @DeadMG et ne recommandent pas l'utilisation de iostreams. En dehors de la mauvaise conception de la performance est souvent pire que celle de plain old style C I/O. je ne collent pas à une bibliothèque d'e/S si, au lieu de cela, j'aimerais créer une interface (classe abstraite) qui possède toutes les opérations requises, par exemple:
Alors vous pouvez implémenter cette interface pour C I/O, iostreams,
mmap
ou quoi que ce soit.Il est probablement possible de le faire, mais j'ai l'impression que ce n'est pas le "droit" de l'utilisation de cette fonction en C++. Le iostream >> et << les opérateurs sont conçus pour une image fidèle des opérations simples, telles que wriitng le "nom, rue, ville, code postal" d'un
class Person
, pas pour l'analyse et le chargement des images. C'est beaucoup mieux fait à l'aide du flux::read() - à l'aide deImage(astream);
, et vous pouvez implémenter un flux de données pour la compression, comme descrtibed par Dietmar.read()
méthode des flux est utilisé à l'intérieur de l'extraction de l'opérateur de lire les données d'image. Mais à la fin il n'a pas vraiment d'importance si le flux est transmis au constructeur, d'un opérateur ou d'une méthode. Le point est de savoir comment créer un flux personnalisé.operator >>
pour aucune utilisation réelle [oui, il a l'air soigné, mais quand vous avez 3-4-5 différents formats d'image de soutien, il sera TRÈS salissant]. Vous pouvez même cacher le flux à l'intérieur de la classe image.operator>>
est probablement pas la réponse pour une image fichier. En fait, la iostream idiome est probablement pas approprié pour les grands, structuré de données binaires; vous avez besoin de quelque chose d'autre. D'autre part, si vous avez de nouvelles, types définis par l'utilisateur, qui analysent les données de texte,operator>>
fonctionne très bien. Dans mon expérience, dans la plupart des applications, presque tous les>>
sera de types définis par l'utilisateur.