OVH Cloud OVH Cloud

redirection de flux dans une application C++

47 réponses
Avatar
Sid Touati
Bonjour à tous,
j'ai cherché sur plusieurs documents et ouvrages de référence la
possibilité ou pas de programmer des redirections de flux localement à
une application C++. Je ne souhaite pas le faire via le shell, car de
telle redirections sont globales à toute l'application.

Je veux faire quelque chose du genre :

ifstream ofile("Toto.txt")

if (!ofile) {
cerr<<"Zut. Pas pu ouvrir. Je lis à partir de stdin à partir de
maintenant.\n";

// ofile=cin ; <===== Je veux implémenter cela. A savoir que
ofile devienne un alias de cin

}

Est-ce possible de faire cela ?

Je sais qu'on peut toujours remplacer ofile par cin dans le reste de
l'application, mais je préfère d'abord voir si on peut aliaser cin avec
un autre ifstream. Cela simplifierait le code.

Je remercie d'avance tout gourou de C++.

S

10 réponses

1 2 3 4 5
Avatar
James Kanze
Marc Boyer wrote:
At après les deux objets écrivent au même endroit ?




Oui exactement, pourquoi pas. N'est ce pas le comportement attendue
d'une redirection ? On fait bien cela en C en une seule instruction,
non ?



Oui, mais, par exemple, C n'a pas d'appel automatique au destructeur.
Donc, l'ambiguïté est laissée à la charge du programmeur.


Plus ou moins. En C, on manipule un pointeur, non l'objet. En C++, on
peut aussi en faire pareil, mais évidemment, dans ce cas-là, l'appel du
destructeur est au charge du programmeur.

Je cite cet aspect puisque c'est le seul que j'ai compris
sur le problème avec rdbuf, et je crois que tu as le même
dans la solution que tu trouvais 'élégante'.


L'utilisation de rdbuf n'est pas forcément mauvaise en soi. Si on veut
changer dynamique la source ou la destination, on n'a pas le choix.
Néaumoins, il froisse mon sens d'élégance :

-- de créer un flux sans streambuf, si on peut l'éviter -- l'idée des
constructeurs n'est-il pas que tout objet qui existe à un état qu'on
peut utiliser --, et

-- de gérer la durée de vie de l'objet streambuf par un [io]stream
interposé, qui ne sert qu'à ça.

Je verrais bien mieux donc quelque chose du genre :

std::filebuf file ;
file.open( ... ) ;
std::istream input( file.is_open()
? &file
: std::cin.rdbuf() ) ;

J'avoue que c'est en partie une question de goût, mais pourquoi
introduire un ifstream dont le seul but est de gérer la durée de vie du
filebuf, alors qu'en le mettant sur la pile, la durée de vie est gérée
automatiquement. Ou en d'autre langage : si je veux un filebuf, je
déclare un filebuf, et non un ofstream.

En ce qui concerne mon exemple, avec la fonction à part, le but était au
moins en partie pédégogique. Ce que je voulais mettre surtout en
évidence, c'est cette distinction streambuf/[io]stream. Dans des cas
simples classiques, j'écrirais bien quelque chose du genre :

std::ifstream file( ... ) ;
if ( file ) {
process( file ) ;
} else {
// Message de log que je n'ai pas pÛ ouvrir file,
// et que j'utilise std::cin à la place...
process( std::cin ) ;
}

(Chose qui, en passant, correspond prèsqu'exactement à ce que je ferais
en C, avec des FILE* et fopen.)

Dès que la logique du choix devient un peu plus complexe, en revanche,
je chercherais bien de le mettre dans une fonction à elle, à part.

if ( argc <= 1 ) {
process( std::cin ) ;
} else {
for ( int i = 1 ; i < argc ; ++ i ) {
std::ifstream in( argv[ i ] ) ;
if ( ! in ) {
std::cerr << "cannot open " << argv[ i ] << std::endl ;
} else {
process( in ) ;
}
}
}

avant de l'avoir encapsuler dans un flux qui le fait automatiquement, de
façon à écrire :

Gabi::MultipleFileInputStream
in( argv + 1, argv + argc, new CannotOpen ) ;
process( in ) ;

. (Le dernier paramètre du constructeur d'in précise la gestion des
erreurs -- que faire si je ne peux pas ouvrir un fichier.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
James Kanze
Sid Touati wrote:

Tout copier, ça veut dire quoi ? Il y a des états implicits,
par exemple ; un ofstream est propriétaire de son streambuf, et
le delete dans son constructeur.



ces questions, on se les pose à chaque fois qu'on surcharge un
opérateur d'affectation entre classes différentes. On arrive bien à
trouver une sémantique pour des cas plus compliqués que les flux
standards...


Oui. Neuf fois sur dix, dès qu'il y a du polymorphisme en vue, et
toujours dès qu'un objet a une identité, on dérive de
boost::noncopiable. Ce qui donne le même résultat de ce qu'on a dans la
norme : on ne peut ni copier ni affecter. En général, on ne définit la
copie et l'affectation que sur des objets à une sémantique de valeur. Ce
qui n'est pas le cas des flux.

Dans la pratique, l'affectation ne marche pas pour des classes du
genre [io]stream. D'abord, parce que l'affectation ne marche pas bien
avec l'héritage ; tu affectes un ofstream à un ostream, et le
résultat est toujours un ostream, jamais un ofstream. (Les deux
objets ne sont donc pas égaux.)



Oui, en effet. Pour me reprendre, cette remarque est vraie dans un cas
plus général, i.e., entre les classes ascendantes et descendantes.


L'autre aspect, à mon avis très important ici, c'est que l'« état »
des objets de flux dépend souvent des conditions à l'extérieur du
programme. C-à-d que si tu as deux flux, a et b, dont un est une copie
de l'autre, l'état d'un change suite à une action sur l'autre. Ce genre
de comportement n'est pas ce que j'attends suite à une affectation. (À
la différence, par exemple, de si j'avais fait :
« a.rdbuf( b.rdbuf ) », où j'ai explicitement appelé une fonction
dont la spécification dit qu'on partage une partie de l'état.)

Je ne comprends pas pourquoi ce comité a rejeté cette
solution.




Parce que personne dans le comité a pû lui donner un sens.



Ou peut être qu'il y a eu plusieurs "sens" proposés mais aucun n'a
fait consensus chez les mandarins.
Il me paraît naturel, et peut être suffisant, de simplement proposer
le sens de l'affectation entre flux comme étant une redirection
classique.


C-à-d ? La rédirection classique (au moins sous Unix) a lieu en dehors
du programme, et donne des objets (qui représente l'ouverture) bien
distincts au niveau du système. Pour l'implémenter avec des ifstream,
par exemple, il faudrait rouvrir le fichier une deuxième fois (au niveau
du système). Ce pose plus d'un problème dans le cas de std::cin, parce
que on n'a pas le nom.

Ensuite, l'implémentation est autre chose. Le thread que j'ai lancé a
proposé certaines pistes.


Si tu rémarques, il a proposé plusieurs pistes différentes. Aucune qui
ne correspond réelement à la rédirection classique, et toutes avec des
contraintes qu'on n'a pas d'habitude avec l'affectation.

On aurait pû le faire, évidemment, avec une définition de l'affectation
qui ressemble un peu à celle d'auto_ptr -- le flux à droite de
l'affectation devient inutilisable. Et que s'il avait la responsibilité
de deleter un streambuf, cette responsibilité passerait au nouveau flux.
Mais il faut avouer que c'est une définition assez curieuse de
l'affectation. Et que l'implémentation porte un coût supplémentaire (il
faudrait que tous les streambuf soient alloués dynamiquement) qu'il
faudrait payer même si on ne s'en servait pas.

Je ne comprends pas. Ici, on fait quelque chose qui n'est même
pas possible en C. Et question de propreté et de lisabilité,
c'est quand même préférable de mettre la logique de décision
dans sa propre fonction, non ?



Je parlais de "ce cas précis", ie les redirections. En C, il est plus
facile de rediriger : on écrit : ofile=stdout; et voilà. Ensuite, il
faut faire attention aux bugs.


En C++, rien n'empêche d'écrire :

std::istream* ofile ;
ofile = &std::cout ;

non plus. C'est même le principe sous certaines solutions proposées
ici : on utilise une référence à la place d'un pointeur, mais le
principe est le même.

Seulement, utiliser la solution C en C++ choque un peu, parce qu'on a
l'habitude de quelque chose de plus simple et surtout de plus robuste.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
James Kanze
Fabien LE LEZ wrote:
On Wed, 19 Jul 2006 10:51:46 +0200, Sid Touati :


En C, il est plus
facile de rediriger : on écrit : ofile=stdout; et voilà.



En C++, tu peux aussi utiliser les FILE*, si tu penses qu'ils sont
plus adaptés à ton problème.


Pour les entrées/sorties binaires, ça pourrait l'être:-). Pas de locale
auquel penser.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
James Kanze
dSid Touati wrote:

Merci pour toutes les réactions intéressantes à ce thread. Mais j'ai
du mal à croire que l'approche orientée objet s'accommode mal avec les
redirections de flux.


Le problème n'est pas là. Le problème, c'est de définir ce que tu
entends par « rédirection ».

Est ce uniquement une difficulté avec la librairie des streams C++ ?
Comment ferait-on en java ?


On ne peut pas en Java. Encore moins qu'en C++.

Je suis également surpris que tout le comité d'experts en C++ n'arrive
pas à trancher sur la sémantique de l'opérateur '=' sur les flux.


Si cela est causé par un véritable un problème de fond sur la
sémantique des objets streams, des équipes de recherche académique en
orienté objet ou en langages de flux seraient (par leur nature) plus
utiles qu'un comité.


Sauf que les équipes académiques qui insistenr sur orientation objet
n'admet pas l'affectation des objets du tout. Seulement des pointeurs.
Si tu veux aller ce chemin en C++, rien ne te l'empêche. Mais fais
gaffe : les programmes Java ont traditionnellement des problèmes de
fuite des descripteurs de fichiers, que l'appel automatiquement des
destructeurs évite en C++. (Je ne dis pas qu'y compter soit toujours une
bonne politique. Surtout en ce qui concerne les fichiers en sortie, tu
veux bien tester l'état après la fermature.) Donc, en C++ :

std::istream* in = new std::ifstream( ... ) ;
if ( ! *in ) {
delete in ;
in = &std::cin ;
}
// ...
if ( in != &std::cin ) {
delete in ;
}

(En Java, on pourrait vraisemblablement faire l'économie du premier
delete. Et c'est une erreur fréquente d'en faire l'économie du séconde.)

Si c'est des problèmes techniques (de conception ou d'implémentation),
c'est autre chose. Les bons éléments trouvent toujours un moyen.


La problème, c'est de trouver une définition de ce que ça veut dire.

Il y a aussi un point de vue (que personnellement je partage) que
l'affectation ne doit concerner que les classes avec une sémantique de
valeur, et que quand un objet de la classe a une identité, la
philosophie de l'orientation objet -- qui interdit l'affectation des
objets -- doit prévaloir.

Je persiste à dire qu'une première définition de l'affectation de flux
en C++ est la redirection. (De mémoire, j'ai lu d'autres sémantiques
dans les langages de flux, mais ce n'est pas du C++).


Pour étayer un peu plus la définition de '=' : Si on écrit a=b, cela
équivaudrait par ex à ce que toute entrée/sortie sur a se fait sur le
buffer de b. Si b est fermé avant a,


Le véritable problèm, c'est que si b cesse d'exister avant a, et que son
buffer cesse alors d'exister.

Et aussi, ce n'est absolument pas la sémantique qu'on attend de
l'affectation. Si je fais a = b, j'ai deux objets distincts, dont les
valeurs sont égales, mais qui ont chacun leur propre vie, et que les
modifications sur un n'ont aucun impact sur l'autre. C'est la définition
de l'affectation en C++ (qui a une sémantique de valeur).

cela se discute :
- On peut imaginer un cas particulier pour cout et cin (si je ne me
trompe pas, ces deux là ne ferment pas dans une application).


Sauf si on les ferme explicitement.

- On peut se débrouiller pour que a continue à être ouvert avec le
dernier état valide de b


- On peut dire que a devient inconsistant
- ....


Je fais confiance aux mandarins pour giberner sur ces points. Mais de
grâce, dire qu'on n'a pas de solution pour la définition de '=' sur
les streams C++ ...


Évidemment, il y a une solution. Il y en a même beaucoup. On peut donner
n'importe quelle sémantique arbitraire à l'affectation, et puis
l'implémenter.

Mais est-ce réelement une bonne idée de donner une sémantique
arbitraire à l'affectation, alors qu'elle a une sémantique assez connue
dans le cas des objets de base, et qui vaut pour tous les types dans la
bibliothèque standard sauf auto_ptr et les iterateurs de flux.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Avatar
Sid Touati


oui.

// includes

std::ifstream ifile("Toto.txt");
std::istream& input = ifile ? static_cast<std::istream&>(ifile) : cin;


J'ai fini par tester cette solution, hélas cela ne marche pas! J'ai des
comportements bizarres (sorties vers le mauvais flux).

Je vais abandonner cette idée de redirection de flux sous C++ avec les
streams.

Merci pour ces multiples messages.

S

Avatar
kanze
Sid Touati wrote:

oui.

// includes

std::ifstream ifile("Toto.txt");
std::istream& input = ifile ? static_cast<std::istream&>(ifile) : cin;


J'ai fini par tester cette solution, hélas cela ne marche pas!
J'ai des comportements bizarres (sorties vers le mauvais
flux).


Tu pourrais être plus précis ? Et indiquer quel compilateur et
quelle version ? Parce que j'utilise cette solution souvent, et
je n'ai jamais eu de problèmes.

Aussi : quand tu dis « sorties » vers le mauvais flux,
s'agit-t-il d'une faute de frapper, ou est-ce que tu as changé
les types ci-dessus en std::ofstream, ou ? Et quand tu dis
« mauvais flux », est-ce que tu veux dire vers std::cout,
quand il aurait fallu vers le fichier, ou vers le fichier, quand
il aurait fallu vers std::cout, ou vers un autre fichier non
nommé. (Note bien que std::ofstream essaie de créer un fichier
s'il peut. Ce qui veut dire que cet idiome sert assez peu avec
les flux de sorties.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Manuel Zaccaria
Sid Touati a écrit:

std::ifstream ifile("Toto.txt");
std::istream& input = ifile ? static_cast<std::istream&>(ifile) : cin;


J'ai fini par tester cette solution, hélas cela ne marche pas! J'ai des
comportements bizarres (sorties vers le mauvais flux).


Impossible! Vous avez fait une erreur. Si vous postiez un bout de code,
on pourrait mieux vous aider...

Je vais abandonner cette idée de redirection de flux sous C++ avec les
streams.


Pourquoi abandonner si vite ?


Manuel


Avatar
Sid Touati
Bon,
j'avoue que mon message n'était pas très explicite, n'aidant pas
beaucoup. Quelques précisions ;

- j'utilise g++ version 4.0.1
- les types ont été changés en std::ofstream.
- J'ai déclaré deux ofstreams que je redirrige si je n'arrive pas à les
créer.
- je dis mauvais flux, car les sorties vers un des deux ofstreams sort
vers le second ofstreams (bienn que les deux fichiers soient créés sans
pb). Bizzare, non ?

Etant donné que le code que je développe ferait partie d'un future
compilateur, vaut mieux pas avoir un code bancal avec des effets de
bord. On n'est pas chez gcc ici :-) (je provoque)

S


Aussi : quand tu dis « sorties » vers le mauvais flux,
s'agit-t-il d'une faute de frapper, ou est-ce que tu as changé
les types ci-dessus en std::ofstream, ou ? Et quand tu dis
« mauvais flux », est-ce que tu veux dire vers std::cout,
quand il aurait fallu vers le fichier, ou vers le fichier, quand
il aurait fallu vers std::cout, ou vers un autre fichier non
nommé. (Note bien que std::ofstream essaie de créer un fichier
s'il peut. Ce qui veut dire que cet idiome sert assez peu avec
les flux de sorties.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Aurélien
Bonjour à tous,
j'ai cherché sur plusieurs documents et ouvrages de référence la
possibilité ou pas de programmer des redirections de flux localement à
une application C++. Je ne souhaite pas le faire via le shell, car de
telle redirections sont globales à toute l'application.



Je n'ai pas pris le temps de lire tout le fil mais il me semble qu'aucune
solution qui te convienne n'ait encore été proposée.
Moi j'utilise parfois celle-ci, peut-être pourra-t-elle t'aider :

#include <iostream>
#include <fstream>
#include <sstream>

class IOredirection
{
private:
std::streambuf* old_in;
std::streambuf* old_out;
std::streambuf* old_err;
std::streambuf* old_log;

public:
IOredirection(void)
{
old_out = std::cout.rdbuf();
old_err = std::cerr.rdbuf();
old_log = std::clog.rdbuf();
old_in = std::cin.rdbuf();
}
~IOredirection(void) { ResetStreams(); }

void setStdOut(std::ostream& out)
{ std::cout.rdbuf(out.rdbuf()); }
void setStdErr(std::ostream& err)
{ std::cerr.rdbuf(err.rdbuf()); }
void setStdLog(std::ostream& log)
{ std::clog.rdbuf(log.rdbuf()); }
void setStdIn(std::istream& in)
{ std::cin.rdbuf(in.rdbuf()); }

void ResetStreams(void)
{
std::cout.rdbuf(old_out);
std::cerr.rdbuf(old_err);
std::clog.rdbuf(old_log);
std::cin.rdbuf(old_in);
}
};


int main(int argc, char **argv)
{
IOredirection io;
std::ofstream out("out.txt"), err("err.txt"), log("log.txt",std::ofstream::app);
io.setStdOut(out);
io.setStdErr(err);
io.setStdLog(log);


cout << "dans le fichier out.txt" << endl;
cerr << "dans le fichier err.txt" << endl;
clog << "dans le fichier log.txt" << endl;
//cin >> variable; // lecture depuis le fichier in.txt
}

Bien sûr il n'est pas nécessaire de tout rediriger.
Enfin pour bien faire les choses il faudrait faire de IOredirection un singleton
mais là ça fonctionne déjà bien, enfin si ça répond à tes attentes.

Dernière remarque, si tu fais la redirection en cours de programme il faut sans
doute utiliser la version suivante pour garder tout l'état des flux :
void setStdOut(std::ostream& out)
{
out.copyfmt(std::cout);
out.clear(std::cout.rdstate());
std::cout.rdbuf(out.rdbuf());
}

Voila, j'espère que je ne suis pas trop à côté de la plaque vu que je n'ai pas
lu tous les messages du fil...

Aurélien Barbier-Accary
--
http://liris.cnrs.fr/aurelien.barbier

Avatar
Michel Decima

Je n'ai pas pris le temps de lire tout le fil mais il me semble
qu'aucune solution qui te convienne n'ait encore été proposée.
Moi j'utilise parfois celle-ci, peut-être pourra-t-elle t'aider :

#include <iostream>
#include <fstream>
#include <sstream>

class IOredirection
{
private:
std::streambuf* old_in;
std::streambuf* old_out;
std::streambuf* old_err;
std::streambuf* old_log;

public:
IOredirection(void)
{
old_out = std::cout.rdbuf();
old_err = std::cerr.rdbuf();
old_log = std::clog.rdbuf();
old_in = std::cin.rdbuf();
}
~IOredirection(void) { ResetStreams(); }

void setStdOut(std::ostream& out)
{ std::cout.rdbuf(out.rdbuf()); }
void setStdErr(std::ostream& err)
{ std::cerr.rdbuf(err.rdbuf()); }
void setStdLog(std::ostream& log)
{ std::clog.rdbuf(log.rdbuf()); }
void setStdIn(std::istream& in)
{ std::cin.rdbuf(in.rdbuf()); }

void ResetStreams(void)
{
std::cout.rdbuf(old_out);
std::cerr.rdbuf(old_err);
std::clog.rdbuf(old_log);
std::cin.rdbuf(old_in);
}
};


int main(int argc, char **argv)
{
IOredirection io;
std::ofstream out("out.txt"), err("err.txt"),
log("log.txt",std::ofstream::app);
io.setStdOut(out);
io.setStdErr(err);
io.setStdLog(log);


cout << "dans le fichier out.txt" << endl;
cerr << "dans le fichier err.txt" << endl;
clog << "dans le fichier log.txt" << endl;
//cin >> variable; // lecture depuis le fichier in.txt
}
Bien sûr il n'est pas nécessaire de tout rediriger.
Enfin pour bien faire les choses il faudrait faire de IOredirection un
singleton mais là ça fonctionne déjà bien, enfin si ça répond à tes
attentes.


Ce besoin de singleton, ce n'est pas parce que le constructeur et
ResetStreams utilisent directement cout, cerr, clog et cin ? Mais
alors tu va perdre la restauration automatique de l'etat anterieur
en fin de portee (et c'est un peut embetant de laisser un flux avec
un rdbuf() qui pointe sur un objet deja detruit...)

Je vois un autre travers dans IOredirection: si je veux imbriquer
des redirections, je suis oblige de faire la restauration "a la main"
parce que ResetStreams() ne correspond pas a mon besoin..
(et on ne peut pas rediriger autre chose que les flux standard)

Dans ce cas, on peut utiliser une classe dont le constructeur
met en place la redirection, et le destructeur retablit la situation
anterieure (l'idiome du saver, en gros, un memento avec RAII):

class IOBufSaver
{
public:
IOBufSaver(std::ios& target, std::ios& new_state)
: m_old_state(target.rdbuf())
, m_target(&target)
{
m_target->rdbuf(new_state.rdbuf());
}

~IOBufSaver() {
m_target.rdbuf(m_old_state);
}
private:
std::streambuf* m_old_state;
std::ios* m_target;
};

On peut maintenant imbriquer les redirections, avec restauration
automatique en fin de portee:

int main(int argc, char **argv)
{
// redirection de cout dans fichier "out.txt"
std::ofstream out("out.txt");
IOBufSaver outGuard(std::cout, out);

...
if (...) {
// redirection temporaire cout dans /dev/null
std::ofstream onull("/dev/null");
IOBufSaver nullGuard(std::cout, onull);
std::ofstream out(std::cout, onull);
}
// on revient dans "out.txt"

...
// le destructeur de outGuard retablit la situation
// anterieure
};

Pour une version complete (et qui marche, parce que je n'ai pas teste
le code que je propose), tu peux voir Boost.IoStateSaver qui contient
tout ce qu'il faut pour faire des savers sur les streams (avec tous
les attributs...)

Dernière remarque, si tu fais la redirection en cours de programme il
faut sans doute utiliser la version suivante pour garder tout l'état
des flux :
void setStdOut(std::ostream& out)
{
out.copyfmt(std::cout);
out.clear(std::cout.rdstate());
std::cout.rdbuf(out.rdbuf());
}


Et aussi la locale, le caractere de remplissage (fill), les flags
de gestion des exceptions, le flux synchronise (tie), et j'en oublie
surement...

1 2 3 4 5