【问题标题】:How can I make an ostream reference an ofstream? (C++)如何使 ostream 引用成为 ofstream? (C++)
【发布时间】:2012-02-27 01:50:48
【问题描述】:

我正在尝试创建一个简单的记录器类,并且我希望能够记录到通用 ostream (cout/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 在构造函数中使用文件名初始化?

【问题讨论】:

    标签: c++ reference ofstream ostream


    【解决方案1】:

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

    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(&amp;os, [](ostream*){}) 使指针指向给定的ostream&amp;,但在超出范围时什么也不做;它给了它一个什么都不做的删除函数。你也可以不使用 lambdas 来做到这一点,只是这个例子更容易。

    【讨论】:

    • 所以,假设我仍在构造函数中创建 ofstream,真的没有任何方法可以避免在堆上声明/删除操作系统,是吗?
    • @Anachrome 不,没有其他办法,因为你想在 one 构造函数(而不是另一个)中创建它,但在构造函数退出后让它最后。我知道的唯一方法是动态分配它。
    • 哎哟!小心使用析构函数! { Log l; } 将尝试致电delete &amp;std::cout;!你需要一面旗帜。
    • 一口井。我会管理的。关于您的代码,如果析构函数是通过第一个构造函数创建的(因此不是动态分配的),在析构函数中删除会不会有问题?当然,我猜这只是使用智能指针的更多理由。
    • 你应该提到为什么需要智能指针。示例代码完全是一场等待发生的灾难,因为编译器认为它的 blitable 并将生成一个复制构造函数。这只是众多潜在问题之一
    【解决方案2】:

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

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

    【讨论】:

    • 这种简单性有一种美感,这实际上让我觉得我最终可能会使用它而不是上面的答案。不过,还没有完全决定。
    【解决方案3】:

    在我的 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;
    

    【讨论】:

      【解决方案4】:

      你必须在构造函数的初始化列表中初始化os,就像你在Log(std::ostream&amp; os = std::cout): os_(os) { }中所做的一样,因为os是一个引用,初始化后不能赋值。

      【讨论】:

      • 仅当他将os 设为对 const 的引用时。
      • @jrok 然后他无法写入或读取它。即便如此,ofstream 也会在构造函数结束时被销毁。
      • @Seth 没错。编辑:不,第二部分不对。对 const 的引用(以及带有它的临时变量)将在类对象的生命周期内有效。
      • @jrok:不,只有局部变量可以延长临时变量的生命周期。成员变量不会。
      • @Martinho。好吧,老实说,我不知道。 :) 谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-23
      • 2023-04-04
      • 1970-01-01
      • 2011-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多