自定义C++ cout类 - 输出到控制台和日志文件

9
我正在开发一个程序,该程序大量使用“cout << strSomething;”将信息记录到控制台。我需要修改程序,以便所有控制台输出都同时发送到控制台和文件中。虽然我可以修改我们代码中的“cout <<”,但由于第三方库的许可证问题,这些库中也使用了“cout <<”,因此修改所有对“cout <<”的引用并不是解决方案。另外,“wtee.exe”的使用也不可能,因为命令行的执行方式不允许。
我使用的是Visual Studio 2008。我看到了Google Groups: redirect cout to file上的帖子,它似乎完全符合我的要求。唯一的问题是代码无法编译。我在->overflow()和->sync()方法调用上得到C2248错误“无法访问受保护的成员”。
有人知道如何使此代码编译吗?或者有没有其他的方法可以同时将cout重定向到控制台和文件?

重复的,我认为:https://dev59.com/aHI-5IYBdhLWcg3wn525 - GManNickG
(它包含一个Boost和非Boost解决方案。) - GManNickG
聪明而简单的解决方案在这里:https://dev59.com/n2Yr5IYBdhLWcg3wE2Oz#13978705 - feetwet
7个回答

12

boost::iostreams::tee_device 是为此而设计的。

#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/tee.hpp>

#include <fstream>
#include <iostream>

int
main()
{
    typedef boost::iostreams::tee_device<std::ostream, std::ofstream> Tee;
    typedef boost::iostreams::stream<Tee> TeeStream;

    std::ofstream file( "foo.out" );
    Tee tee( std::cout, file );

    TeeStream both( tee );

    both << "this goes to both std::cout and foo.out" << std::endl;

    return 0;
}

示例调用:

samm$ ./a.out
this goes to both std::cout and foo.out
samm$ cat foo.out
this goes to both std::cout and foo.out
samm$ 

这对我来说完全没问题 - 不幸的是,我被要求在此应用程序中不包括Boost。这是管理层基于非技术因素的决定。那么 - 你如何只使用std来完成这个任务? - Jason Swager
@Jason,看起来有一些其他的答案没有使用boost库。祝你好运。 - Sam Miller

3

这可以轻松地扩展到其他流。

OstreamFork.hpp -- 将数据同时分发到2个流

#include <iomanip>
#include <fstream>
#include <iostream>
using namespace std ;

class ostreamFork           // Write same data to two ostreams
{
public:
  ostream& os1 ;
  ostream& os2 ;

  ostreamFork( ostream& os_one , ostream& os_two )
  : os1( os_one ) ,
    os2( os_two )
  {}

 } ;

                          // For data: int, long , ...
 template <class Data>
 ostreamFork& operator<<( ostreamFork& osf , Data d )
 {
   osf.os1 << d ; 
   osf.os2 << d ;
   return osf ;
 }
                        // For manipulators: endl, flush
 ostreamFork& operator<<( ostreamFork& osf , ostream& (*f)(ostream&)  )
 {
   osf.os1 << f ; 
   osf.os2 << f ;
   return osf ;
 }

                            // For setw() , ...
template<class ManipData>
 ostreamFork& operator<<( ostreamFork& osf , ostream& (*f)(ostream&, ManipData )  )
 {
   osf.os1 << f ; 
   osf.os2 << f ;
   return osf ;
 }

TestOstreamFork.cpp:

#include "stdafx.h"
#include <fstream>
  using namespace std ;
#include "ostreamFork.hpp"

int main(int argc, char* argv[])
{
  ofstream file( "test2.txt" ) ;
  ostreamFork osf( file , cout ) ;

  for ( int i = 0 ; i < 10 ; i++ )
  {
    osf << i << setw(10) << " " << 10*i << endl  ;
  }

    return 0 ;
}

同时将输出发送到cout和test2.txt:

0          0
1          10
2          20
3          30
4          40
5          50
6          60
7          70
8          80
9          90

2

如果你非常着急:

#define protected public
#include <iostream>
#undef protected

这是一个比较粗糙的方法,但通常可以起作用。


此时此刻,我已经绝望到甚至愿意采用丑陋的黑客方式。不幸的是,使用VS2008也没有帮助。仍然是同样的错误。 - Jason Swager

1
你可以通过使用指向std::streambuf的指针来捕获std::cout.rdbuf(),然后我认为你应该能够将所有输出写入到某个文件中。

1

sync调用可以替换为pubsync。至于overflow调用,我认为那可能是一个打字错误,因为它看起来应该是对sputc的调用。


将“sync”更改为“pubsync”可以编译。但是将“overflow”更改为“putc”会失败。错误C2039:“putc”不是std::basic_streambuf<_Elem,_Traits>的成员。 - Jason Swager
抱歉,我的答案中打错了一个字。我更正为sputc - Troubadour
在我们的情况下(无法使用Boost,无法修改第三方库),这是最佳解决方案。 - Jason Swager

0

很抱歉这么晚才回复,但这里应该有一个基于Dietmar Kühl在Google群组中的解决方案,将cout重定向到一个teebuffer。

使用方法非常简单。

GetSetLog log("myfile.log");

在对象“log”生命周期内,所有内容都将被写入到cout/cerr和文件中。

https://sourceforge.net/p/getset/code/ci/master/tree/GetSet/GetSetLog.hxx


0

你可以使用包装类来实现,例如:

#include <iostream>
#include <fstream>

...

class streamoutput
{
    std::ofstream fileoutput;
    public:
    streamoutput(char*filename){
        fileoutput.open(filename);
    }
    ~streamoutput(){
        fileoutput.close();
    }
    template<class TOut> streamoutput& operator <<(const TOut& data)
    {
        fileoutput << data;
        std::cout << data;
        return this;
    }
};

extern streamoutput cout("logfile.log");

像这样声明cout,然后将所有#include <iostream>更改为包含此包装器(记住,cout是外部变量,因此您也必须在其中一个源代码中声明它)。


这对我们编写的代码有效,我们可以修改我们的#include <iostream>行。但是对于第三方代码来说,这将无效,因为他们也必须使用#include <iostream>,但由于许可证问题,我们无法进行修改。 - Jason Swager
那么你可以直接修改iostream文件!没有任何许可证可以阻止你这样做!但是如果你有一个预编译的代码,那就完全不同了,但你也可以编写一个包装程序来运行你的代码,并为你完成所有控制台工作。虽然这需要更多的努力! - Ali1S232

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