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

7 réponses

1 2 3 4 5
Avatar
kanze
Aurélien wrote:
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::ofstr eam::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
}


Je définirais l'instance de IOredirection *après* la création
des flux cibles. Je n'aime pas l'idée qu'on va détruire les flux
(ou plutôt les streambuf qu'ils contiennent) et que std::cin
etc. ont encore des pointeurs à ces flux. Il y a, d'ailleurs,
une dépendance ici qui ne me plaît pas du tout. Imagine, par
exemple, que quelqu'un définit une variable entre la définition
de io et la définition out, err, etc., et que le destructeur de
cette variable utilise un des flux.

En général, je n'aime pas l'idée que cout n'est pas cout, mais
quelque chose d'autre -- mais évidemment, je ne sors jamais
vers cout, mais vers des ostream& qui sont peut-être
initialisées à cout, peut-être initialisées à quelque chose
d'autre. Mais si tu veux vraiment poursuivre cette idée, ce que
je conseillerais, c'est un objet par flux, qui contient le
[io]stream ; quelque chose du genre :

RedirectedOutput out( std::cout, "out.txt" ) ;

où cout sort vers le fichier "out.txt" pour la durée de vie de
l'objet out.

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


Surtout pas. Là, tu es pratiquement garanti d'avoir des
problèmes dans les destructeurs des objets statiques.

mais là ça fonctionne déjà bien, enfin si ça répond à tes
attentes.


Ça fonctionne déjà bien dans certains cas bien restreints, oui.
Ça m'a l'air extrèmement fragile à l'utilisation.

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());
}


Mais voilà la question ? Est-ce qu'il veut garder tout l'état
du flux ? Que doit-on faire si le fichier de sortie n'utilise
pas le même encodage que la sortie standard, par exemple ?
(Si j'ai bien compris ton code, c'est le seul état que tu copies
qui va servir.) Et j'aurais bien vu une copie dans l'autre sens
de l'état d'erreur :
std::cout.setstate( out.rdstate() ) ;
(et évidemment l'inverse dans le release) ; si le fichier est
en état d'erreur, on ne veut pas perdre cette information.

--
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
kanze
Michel Decima wrote:

[...]
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...


C'est tout compris dans le copyfmt. Copyfmt copie tout sauf
rdbuf() et rdstate(). Mais à mon avis, il ne faut pas le copier.
Si on n'utilise pas out, par la suite, la seule chose qui aurait
un effet, c'est l'encodage (le locale du streambuf), qui est a
priori une caractèristique du fichier, et non du flux. Si on
utilise out par la suite, en revanche, je crois qu'il serait un
peu étonnant que ses paramètres de formattage change suite à
cette fonction.

Aussi, je n'aime pas du tout le fait qu'une erreur sur out
disparaisse. J'aurais vu une copie de l'état dans l'autre
sens ; qu'une erreur présente sur out apparaisse alors sur
cout. (Mais attention alors de l'ordre des copies ; la
positionnement d'une erreur peut provoquer une exception.)

[Pour la reste, je crois que nos critiques vont dans le même
sens.]

--
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
Michel Decima
Michel Decima wrote:

[...]
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...


C'est tout compris dans le copyfmt. Copyfmt copie tout sauf
rdbuf() et rdstate(). Mais à mon avis, il ne faut pas le copier.


Effectivement, j'ai confondu copyfmt() avec flags()...

A ce propos, je cherche une methode pour reinitialiser completement
les attributs d'un flux (flags, witdh, precision etc.) pour revenir
a l'etat initial (celui du constructeur). Est ce que l'ecriture

flux.copyfmt(std::ios(NULL));

pourrait correspondre a mon besoin (j'ai un gros doute sur le
parametre NULL) ?
Je sais que ce n'est pas tres "propre", mais la bonne solution
serait de toucher au design, et je ne veut pas le faire tout
de suite, meme si c'est prevu.

Aussi, je n'aime pas du tout le fait qu'une erreur sur out
disparaisse. J'aurais vu une copie de l'état dans l'autre
sens ; qu'une erreur présente sur out apparaisse alors sur
cout. (Mais attention alors de l'ordre des copies ; la
positionnement d'une erreur peut provoquer une exception.)


La derniere fois que j'ai eu a rediriger un flux, j'avais
fait la copie de l'état dans ce sens, pour la raison que tu
cites. Mais je n'avais pas pris en compte le probleme des
exceptions (que je n'ai jamais utilise sur les flux, d'ailleurs).

[Pour la reste, je crois que nos critiques vont dans le même
sens.]


Oui.



Avatar
kanze
Michel Decima wrote:

[...]
A ce propos, je cherche une methode pour reinitialiser
completement les attributs d'un flux (flags, witdh, precision
etc.) pour revenir a l'etat initial (celui du constructeur).
Est ce que l'ecriture

flux.copyfmt(std::ios(NULL));

pourrait correspondre a mon besoin (j'ai un gros doute sur le
parametre NULL) ?


À mon avis, ça doit marcher. NULL est explicitement permis comme
paramètre, avec un effet sur les post-conditions (rdstate()
renvoie badbit, plutôt que goodbit).

Je sais que ce n'est pas tres "propre", mais la bonne solution
serait de toucher au design, et je ne veut pas le faire tout
de suite, meme si c'est prevu.


Je sais même pas ce qu'il y a de malpropre là dedans. Mais c'est
vrai qu'il manque un moyen facile. (Je crois qu'il y en a une
proposition pour la prochaine version de la norme.)

Aussi, je n'aime pas du tout le fait qu'une erreur sur out
disparaisse. J'aurais vu une copie de l'état dans l'autre
sens ; qu'une erreur présente sur out apparaisse alors sur
cout. (Mais attention alors de l'ordre des copies ; la
positionnement d'une erreur peut provoquer une exception.)


La derniere fois que j'ai eu a rediriger un flux, j'avais fait
la copie de l'état dans ce sens, pour la raison que tu cites.
Mais je n'avais pas pris en compte le probleme des exceptions
(que je n'ai jamais utilise sur les flux, d'ailleurs).


Moi nom plus. Et l'histoire des exceptions peut être genant. A
priori, tu ne veux pas les changer, bien que le copyfmt le fait.
Je crois que la meilleur solution, c'est de sauvegarder l'ancien
état (de la destination), puis les désactiver. Après toutes les
copies, on réstitue l'état initial des exceptions (et si il y
avait une erreur dans la source, et que les exceptions étaient
actives, il y aura une exception).

--
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
Michel Decima
Michel Decima wrote:

flux.copyfmt(std::ios(NULL));

pourrait correspondre a mon besoin (j'ai un gros doute sur le
parametre NULL) ?


À mon avis, ça doit marcher. NULL est explicitement permis comme
paramètre, avec un effet sur les post-conditions (rdstate()
renvoie badbit, plutôt que goodbit).


Ok, alors ca me va, et ca me simplifie la vie.

Je sais que ce n'est pas tres "propre", mais la bonne solution
serait de toucher au design, et je ne veut pas le faire tout
de suite, meme si c'est prevu.


Je sais même pas ce qu'il y a de malpropre là dedans. Mais c'est
vrai qu'il manque un moyen facile. (Je crois qu'il y en a une
proposition pour la prochaine version de la norme.)


Ce que je trouve malpropre, c'est avoir besoin de passer par un
temporaire, avec un parametre NULL (et il faut verifier que ce
parametre est autorise, merci James ;), ca peut choquer a la
lecture plus tard. J'aurais prefere une fonction resetfmt(),
mais il n'y a pas actuellement.

La derniere fois que j'ai eu a rediriger un flux, j'avais fait
la copie de l'état dans ce sens, pour la raison que tu cites.
Mais je n'avais pas pris en compte le probleme des exceptions
(que je n'ai jamais utilise sur les flux, d'ailleurs).


Moi nom plus. Et l'histoire des exceptions peut être genant. A
priori, tu ne veux pas les changer, bien que le copyfmt le fait.
Je crois que la meilleur solution, c'est de sauvegarder l'ancien
état (de la destination), puis les désactiver. Après toutes les
copies, on réstitue l'état initial des exceptions (et si il y
avait une erreur dans la source, et que les exceptions étaient
actives, il y aura une exception).


J'aime bien ce raisonnement.


Avatar
Michel Decima
Michel Decima wrote:

Est ce que l'ecriture

flux.copyfmt(std::ios(NULL));

pourrait correspondre a mon besoin (j'ai un gros doute sur le
parametre NULL) ?


À mon avis, ça doit marcher. NULL est explicitement permis comme
paramètre, avec un effet sur les post-conditions (rdstate()
renvoie badbit, plutôt que goodbit).


Haha! ca marche pas! Il semble que le constructeur de copie de
std::ios est privé, et comme l'écriture ci dessus necessite un
temporaire, il faut l'accès au constructeur de copie:

#include <ios>
#include <sstream>

int main() {
std::ostringstream flux;
flux.copyfmt(std::ios(NULL));
}

$ xlC -c flux.C
"flux.C", line 6: (W)
The "private" copy constructor "basic_ios(const_Myt &)" cannot be
accessed. The semantics specify that a temporary object must be
constructed. The temporary is not constructed, but the copy constructor
must be accessible.

$ xlC -qlanglvl=strict98 -c flux.C
(S) The "private" copy constructor "std::basic_ios<char,
std::char_traits<char> >::basic_ios(const _Myt &)" cannot be accessed to
create a temporary object.

Voila ce que me dit xlC-6. J'ai essayé avec g++-4.1, ca ne compile pas
non plus, mais les messages sont beaucoup moins explicites (et qu'est-ce
dont qu'une synthetised method?)

Evidemment, si on ecrit

std::ostringstream flux;
std::ios cleaner(NULL);
flux.copyfmt(cleaner);

ca compile beaucoup mieux.


Avatar
kanze
Michel Decima wrote:
Michel Decima wrote:

Est ce que l'ecriture

flux.copyfmt(std::ios(NULL));

pourrait correspondre a mon besoin (j'ai un gros doute sur
le parametre NULL) ?


À mon avis, ça doit marcher. NULL est explicitement permis
comme paramètre, avec un effet sur les post-conditions
(rdstate() renvoie badbit, plutôt que goodbit).


Haha! ca marche pas! Il semble que le constructeur de copie de
std::ios est privé, et comme l'écriture ci dessus necessite un
temporaire, il faut l'accès au constructeur de copie:


En effect, j'ai oublié ce point-là. Les flux ne sont pas
copiables.

[...]
Evidemment, si on ecrit

std::ostringstream flux;
std::ios cleaner(NULL);
flux.copyfmt(cleaner);

ca compile beaucoup mieux.


J'allais justement te le dire:-).

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



1 2 3 4 5