【问题标题】:How to switch between std::ofstream and std::cerr如何在 std::ofstream 和 std::cerr 之间切换
【发布时间】:2019-08-24 18:34:05
【问题描述】:

我想编写一个函数,该函数返回一个 ostream,该 ostream 可以写入文件或 stderr。

我的第一次尝试:

#include <fstream>
#include <iostream>

std::ostream get_stream() {
    bool flag = (time(nullptr) % 2); // stand-in
    if (flag)
        return std::cerr;
    else
        return std::ofstream{"somefile.txt"};
}

int main() {
    auto logger {get_stream()};
    logger << "Just testing, everything is fine."
           << std::endl;
}

这会因(长)编译器错误而失败 - 我怀疑是因为 std::cerr 没有复制构造函数。另一个变体(返回引用)不起作用,因为 ofstream 是一个局部变量。我可以在堆上分配ofstream,但是调用者不知道是否需要释放指针(当我们返回对std:cerr 的引用时它不会)。

所以,我写了第 2 版:

#include <fstream>
#include <iostream>

struct Logger {
    std::ofstream ostream;
    bool to_stderr {true};

    template<typename A>
    Logger& operator<<(A rhs) {
        if (to_stderr)
            std::cerr << rhs;
        else
            ostream << rhs;
        return this;
    }
};

int main() {
    Logger logger;
    logger << "Just testing, everything is fine."
           << std::endl;
}

这也会失败,并出现以以下开头的长编译错误:

$ g++ -Wall -o v2 v2.cpp
v2.cpp: In function ‘int main()’:
v2.cpp:30:12: error: no match for ‘operator<<’ (operand types are ‘Logger’ and ‘<unresolved overloaded function type>’)
     logger << "Just testing, everything is fine."
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            << std::endl;
            ^~~~~~
v2.cpp:9:13: note: candidate: template<class A> Logger& Logger::operator<<(A)
     Logger& operator<<(A rhs) {
             ^~~~~~~~
v2.cpp:9:13: note:   template argument deduction/substitution failed:
v2.cpp:30:20: note:   couldn't deduce template parameter ‘A’
            << std::endl;
                    ^~~~
[...]

为什么第二个版本不工作,实现这样的事情的正确方法是什么?

(Using << operator to write to both a file and cout 是一个相关问题,但据我所知,解决方案的唯一区别是operator&lt;&lt; 是在类外部定义的,如果我对我的代码进行相同的更改,我'我仍然得到同样的错误)

【问题讨论】:

  • 您不能按值传递或返回流。
  • 如果get_stream() 返回std::ostream&amp;,第一个将起作用。
  • @PeteBecker 返回引用修复了我们想要返回 cerr 的情况,但它破坏了返回文件流,因为这是一个局部变量。
  • @Nikratio - 所以不要让它成为局部变量。

标签: c++ stl


【解决方案1】:

您必须动态分配流:

std::unique_ptr<std::ostream> get_stream() {
  bool flag = time(0) % 2;
  return ( flag
    ? std::make_unique<std::ostream>(std::cerr.rdbuf())
    : std::make_unique<std::ofstream>("somefile.txt")
  );
}

int main() {
  auto logger {get_stream()};
  *logger << "Just testing, everything is fine."
          << std::endl;
}

【讨论】:

  • 这看起来很有希望,谢谢!我认为这里真正的技巧不是动态分配,而是使用cerr.rdbufstd:cerr 构造一个新流。你能说更多关于这方面的作品吗?这总是安全的吗?写入std:cerr 和“其他”cerr 是否会正确同步?
  • @Nikratio 不,它们不会被同步(如果我理解你对同步这个词的意思)。 std::cerr 默认与std::cout 绑定,因此std::cerr 上的任何输出操作都将刷新std::cout,使它们保持同步。此示例中返回的流不是这种情况。如果您想要std::cerr 的完整副本,您将在调用构造函数后使用copyfmt? []{auto o=std::make_unique&lt;std::ostream&gt;(std::cerr.rdbuf());o-&gt;copyfmt(std::cerr);return o;}()
【解决方案2】:

您只需将文件流提升到全局状态。这可以是全局变量、静态成员或(我最喜欢的)块局部静态变量。这种方法确保文件流在第一次调用之前不会被打开,并且会一直保持打开状态直到程序终止。

从那里您只需通过引用返回流。

std::ostream &get_stream() {
    static std::ofstream file("somefile.txt");
    return flag ? std::cerr : file;
}

【讨论】:

  • 另请注意,在main() 中,需要将记录器定义为参考:auto&amp; loggerdecltype(auto) logger
  • 将其提升为全局变量是可能的,因为该示例故意简单。实际代码中,文件名不同,可能同时使用多个流。
猜你喜欢
  • 1970-01-01
  • 2011-10-29
  • 2013-03-12
  • 1970-01-01
  • 2011-09-02
  • 2020-11-05
  • 1970-01-01
  • 2016-05-13
  • 2015-08-16
相关资源
最近更新 更多