Comment puis-je faire une référence OSstream un OFTREAM? (C ++)
Question
J'essaye de faire une classe d'enregistrement simple, et je veux la possibilité de se connecter à un générique ostream
(cout
/cerr
) ou un fichier. Le design que j'ai en tête est de permettre au constructeur de prendre un ostream&
ou un nom de fichier, et dans ce dernier cas, créez un ofstream&
et attribuer cela à la classe «privée ostream&
ainsi:
class Log {
private:
std::ostream& os;
public:
Log(std::ostream& os = std::cout): os(os) { }
Log(std::string filename) {
std::ofstream ofs(filename);
if (!ofs.is_open())
// do errorry things
os = ofs;
}
};
Faire tel me donne une erreur qui ofstream
L'opérateur d'affectation est privé. En regardant à nouveau par-dessus, il m'est venu à l'esprit que faire une référence à un objet local ne fonctionnerait probablement pas, et faire os
un pointeur vers un ostream
et le déclarer et le supprimer sur le tas ont travaillé avec le ofstream
cas, mais pas avec le ostream
cas, où le ostream
existe déjà et est juste référencé par os
(Parce que le seul endroit pour supprimer os
serait dans le constructeur, et je ne connais pas un moyen de déterminer si os
pointe vers un ofstream
créé sur le tas ou non).
Alors comment puis-je faire ce travail, c'est-à-dire faire os
référence à ofstream
initialisé avec un nom de fichier dans le constructeur?
La solution
D'une part, vous ne pouvez pas re-rediger des références une fois qu'ils sont créés, vous ne pouvez que les initialiser. Vous pourriez penser que vous pourriez faire ceci:
Log(std::string filename) : os(std::ofstream(filename)) {
if (!os.is_open())
// do errorry things
}
Mais ce n'est pas bon parce que tu fais os
Reportez-vous à une variable temporaire.
Lorsque vous avez besoin d'une référence qui doit être optionnel, c'est-à-dire qu'il doit se référer à quelque chose parfois et pas d'autres fois, ce dont vous avez vraiment besoin est un aiguille:
class Log {
private:
std::ostream* os;
bool dynamic;
public:
Log(std::ostream& os = std::cout): os(&os), dynamic(false) { }
Log(std::string filename) : dynamic(true) {
std::ofstream* ofs = new std::ofstream(filename);
if (!ofs->is_open())
// do errorry things and deallocate ofs if necessary
os = ofs;
}
~Log() { if (dynamic) delete os; }
};
L'exemple ci-dessus est simplement de vous montrer ce qui se passe, mais vous voudrez probablement le gérer avec un pointeur intelligent. Comme le souligne Ben Voigt, il y a beaucoup de gotchas qui provoqueront un comportement imprévu et indésirable dans votre programme; Par exemple, lorsque vous essayez de faire une copie de la classe ci-dessus, il appuyera sur le ventilateur. Voici un exemple de ce qui précède en utilisant des pointeurs intelligents:
class Log {
private:
std::unique_ptr<std::ostream, std::function<void(std::ostream*)>> os;
public:
Log(std::ostream& os = std::cout): os(&os, [](ostream*){}) { }
Log(std::string filename) : os(new std::ofstream(filename), std::default_delete<std::ostream>()) {
if (!dynamic_cast<std::ofstream&>(*os).is_open())
// do errorry things and don't have to deallocate os
}
};
L'inhabituel os(&os, [](ostream*){})
fait que le pointeur pointe vers le don ostream&
Mais ne faites rien quand il sort de la portée; Il lui donne une fonction de déleter qui ne fait rien. Vous pouvez aussi le faire sans Lambdas, c'est juste plus facile pour cet exemple.
Autres conseils
La chose la plus simple à faire est simplement de lier votre référence à un ofstream
, et assurez-vous que le ofstream
vit aussi longtemps que votre objet:
class Log
{
std::ofstream byname;
std::ostream& os;
public:
Log(std::ostream& stream = std::cout) : byname(), os(stream) { }
Log(std::string filename) : byname(filename), os(this->byname)
{
if (!os)
// handle errors
}
};
Exception Safe, ne peut pas fuir et les fonctions de membres spéciaux générées par le compilateur sont saines.
Dans ma classe log / débogage, je trouve utile de créer une variable de membre statique:
class debug {
public:
...
// Overload operator() for printing values.
template<class Type1>
inline debug&
operator()(const std::string& name1,
const Type1& value1)
{
// Prettify the name/value someway in another inline function.
_stream << print_value(name1, value1) << std::endl;
return *this;
}
private:
...
static std::ostream& _stream;
};
Et puis dans mon fichier debug.cc:
std::ostream& debug::_stream = std::cerr;
Vous devez initialiser le os
dans la liste d'initialisation des constructeurs, tout comme ce que vous avez fait en Log(std::ostream& os = std::cout): os_(os) { }
, car os
est une référence, qui ne peut être attribuée après l'initialisation.