Как я могу сделать ссылку ostream на ofstream? (C ++) - PullRequest
9 голосов
/ 27 февраля 2012

Я пытаюсь создать простой класс логгера, и я хочу иметь возможность либо вести запись либо в общий ostream (cout / cerr), либо в файл.Проект, который я имею в виду, состоит в том, чтобы позволить конструктору либо взять ostream& или имя файла, а в последнем случае создать ofstream& и присвоить его классу private ostream& следующим образом:

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

При выполнении этого я получаю ошибку, что оператор присваивания ofstream является частным.Смотря на это снова, мне пришло в голову, что создание ссылки на локальный объект, вероятно, не сработает, и создание os указателя на ostream, а объявление и удаление его в куче работает в случае ofstreamхотя и не в случае ostream, где ostream уже существует и на него ссылается os (поскольку единственное место для удаления os будет в конструкторе, а я не знаюспособ определить, действительно ли os указывает на ofstream, созданный в куче или нет).

Так как я могу заставить эту работу работать, то есть заставить os ссылаться на ofstream, инициализированный симя файла в конструкторе?

Ответы [ 4 ]

10 голосов
/ 27 февраля 2012

С одной стороны, вы не можете перепривязать ссылки после их создания, вы можете только инициализировать их. Вы можете подумать, что можете сделать это:

Log(std::string filename) : os(std::ofstream(filename)) {
    if (!os.is_open())
        // do errorry things
}

Но это нехорошо, потому что вы заставляете os ссылаться на временную переменную.

Когда вам нужна ссылка, которая должна быть необязательной , то есть она должна ссылаться на что-то иногда, а не в другое время, вам действительно нужен указатель :

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

Приведенный выше пример просто показывает вам, что происходит, но вы, вероятно, захотите управлять им с помощью умного указателя. Как указывает Бен Фойгт, есть много ошибок, которые могут вызвать непредвиденное и нежелательное поведение в вашей программе; например, когда вы попытаетесь сделать копию вышеупомянутого класса, он поразит поклонника. Вот пример вышесказанного с использованием умных указателей:

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

Необычный os(&os, [](ostream*){}) делает указатель указателем на данный ostream&, но ничего не делает, когда он выходит из области видимости; это дает ему функцию удаления, которая ничего не делает. Вы можете сделать это и без лямбд, в этом примере это проще.

8 голосов
/ 27 февраля 2012

Самое простое, что нужно сделать, это просто связать вашу ссылку с ofstream и убедиться, что ofstream живет столько же, сколько и ваш объект:

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

Исключение безопасно, не может течь,и сгенерированные компилятором специальные функции-члены являются нормальными.

0 голосов
/ 27 февраля 2012

В моем классе Log / Debug я считаю полезным создать статическую переменную-член:

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

А затем в моем файле debug.cc:

std::ostream& debug::_stream = std::cerr;
0 голосов
/ 27 февраля 2012

Вы должны инициализировать os в списке инициализации в конструкторах, так же как и в Log(std::ostream& os = std::cout): os_(os) { }, потому что os является ссылкой, которую нельзя назначить после инициализации.

...