从std::cout或std::ofstream(file)中获取一个std::ostream对象

61

如何在程序的特定条件下将std::ostream绑定到std::coutstd::ofstream对象?尽管出于许多原因而无效,但我想实现与以下代码在语义上等效的内容:

std::ostream out = condition ? &std::cout : std::ofstream(filename);

我已经看到一些不符合异常安全的示例,比如来自http://www2.roguewave.com/support/docs/sourcepro/edition9/html/stdlibug/34-2.html的一个示例:

int main(int argc, char *argv[])
{
  std::ostream* fp;                                           //1
  if (argc > 1)
     fp = new std::ofstream(argv[1]);                         //2
  else
     fp = &std::cout                                          //3

  *fp << "Hello world!" << std::endl;                         //4
  if (fp!=&std::cout) 
     delete fp;
}

有没有人知道一个更好的、异常安全的解决方案?

6个回答

76
std::streambuf * buf;
std::ofstream of;

if(!condition) {
    of.open("file.txt");
    buf = of.rdbuf();
} else {
    buf = std::cout.rdbuf();
}

std::ostream out(buf);

这将关联cout或输出文件流的底层streambuf到"out"。之后,您可以向"out"写入内容,并且它将被正确地发送到目标位置。如果您只想将所有发送至std::cout的内容发送到文件中,您也可以执行以下操作:

std::ofstream file("file.txt");
std::streambuf * old = std::cout.rdbuf(file.rdbuf());
// do here output to std::cout
std::cout.rdbuf(old); // restore

第二种方法存在不安全的缺陷。您可能想编写一个使用 RAII 的类来执行此操作:

struct opiped {
    opiped(std::streambuf * buf, std::ostream & os)
    :os(os), old_buf(os.rdbuf(buf)) { }
    ~opiped() { os.rdbuf(old_buf); }

    std::ostream& os;
    std::streambuf * old_buf;
};

int main() {
    // or: std::filebuf of; 
    //     of.open("file.txt", std::ios_base::out);
    std::ofstream of("file.txt");
    {
        // or: opiped raii(&of, std::cout);
        opiped raii(of.rdbuf(), std::cout);
        std::cout << "going into file" << std::endl;
    }
    std::cout << "going on screen" << std::endl;
}

现在,无论发生什么情况,std::cout 都处于干净状态。

哦,我更喜欢std::streambuf而不是std::ostream - Tom
但是如果有什么东西抛出异常,你将会在已经被销毁的 ofstream 的 streambuf* 的 cout 中留下一个悬挂指针。所以要小心,最好编写一个 RAII 方法。 - Johannes Schaub - litb
2
我不喜欢劫持 std::cout。这意味着 cout 和 printf 不再等同,而这是许多开发人员认为理所当然的事情。 - Tom
如果 out 是类的成员,我是否可以以某种方式使用这种方法? - René Nyffenegger
3
如何关闭与对象当前关联的文件,即对于第一个情况 of.open("file.txt"); - InvisibleWolf

28

这是异常安全的:

void process(std::ostream &os);

int main(int argc, char *argv[]) {
    std::ostream* fp = &cout;
    std::ofstream fout;
    if (argc > 1) {
        fout.open(argv[1]);
        fp = &fout;
    }
    process(*fp);
}

编辑:Herb Sutter在文章Switching Streams (Guru of the Week)中对此进行了讨论。



这似乎并不比原始代码更安全。 - Brian
是的。如果在处理 (*fp << "Hello World" << std::endl) 时抛出异常,原始代码会存在内存泄漏问题。 - Tom
3
我为什么会投票支持这个答案,是因为第一个答案打破了我的一个旧规则:“不要干涉其他对象的内部”。仅仅因为你可以替换rdbuf并不意味着你应该这样做。 - dex black
2
Herb的代码使用?:运算符,第二个和第三个参数类型不同以及左值/右值性质,这是无效的,并且在现代编译器(如Comeau Online或MSVC 10.0)中无法编译。我已经给他发了邮件。但在它被修复之前,也许在答案中做个注释,指出链接到的GOTW代码是无效的。干杯! - Cheers and hth. - Alf

8
std::ofstream of;
std::ostream& out = condition ? std::cout : of.open(filename);

6
它怎么能编译呢?std::ofstream::open 的返回类型是 void - Haoshu

3

参考自这篇文章,你可以采用类似的方法。

struct noop {
    void operator()(...) const {}
};

std::shared_ptr<std::ostream> of;
if (condition) {
    of.reset(new std::ofstream(filename, std::ofstream::out));
} else {
    of.reset(&std::cout, noop());
}

-1
作为一个C++的新手,我不知道这是否是异常安全的,但这是我通常的做法:
std::ostream& output = (condition)?*(new std::ofstream(filename)):std::cout;

永远不要使用原始的 new,尤其不要像这样使用。 - Felix Dombek

-4
以下简单代码适用于我:
int main(int argc, char const *argv[]){   

    std::ofstream outF;
    if (argc > 1)
    {
        outF = std::ofstream(argv[1], std::ofstream::out); 
    }

    std::ostream& os = (argc > 1)? outF : std::cout;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接