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 ofstreamL'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?

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top