11

我正在尝试制作一个简单的记录器类,并且我希望能够记录到通用ostreamcout/ cerr)或文件。我想到的设计是允许构造函数采用一个ostream&或一个文件名,并在后一种情况下创建一个ofstream&并将其分配给类的私有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

4 回答 4

11

一方面,一旦创建引用,就不能重新绑定引用,只能初始化它们。你可能认为你可以这样做:

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

上面的示例只是向您展示发生了什么,但您可能希望使用智能指针来管理它。正如 Ben Voigt 所指出的,有很多陷阱会在您的程序中导致无法预料和不受欢迎的行为;例如,当您尝试复制上述类时,它会撞到风扇。以下是使用智能指针的上述示例:

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&但超出范围时不执行任何操作;它给了它一个什么都不做的删除函数。你也可以在没有 lambda 的情况下做到这一点,这个例子更容易。

于 2012-02-27T01:57:12.007 回答
9

最简单的做法是将您的引用绑定到一个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
    }
};

异常安全,不能泄漏,编译器生成的特殊成员函数是理智的。

于 2012-02-27T02:26:33.127 回答
0

您必须os在构造函数的初始化列表中初始化 ,就像您在 中所做的那样Log(std::ostream& os = std::cout): os_(os) { },因为os它是一个引用,初始化后无法分配。

于 2012-02-27T01:58:47.240 回答
0

在我的 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;
于 2012-02-27T02:51:16.940 回答