如何在C++中覆盖cout输出?

8

我有一个需求,需要使用printfcout将数据显示在控制台和文件中。 对于printf,我已经完成了,但是对于cout,我正在苦恼,该如何做?

   #ifdef _MSC_VER
     #define GWEN_FNULL "NUL"
     #define va_copy(d,s) ((d) = (s))
         #else
         #define GWEN_FNULL "/dev/null"
        #endif
        #include <iostream>
        #include <fstream>

        using namespace std;
        void printf (FILE *  outfile, const char * format, ...) 
        {

            va_list ap1, ap2;
            int i = 5;
            va_start(ap1, format);
            va_copy(ap2, ap1);
            vprintf(format, ap1);
            vfprintf(outfile, format, ap2);
            va_end(ap2);
            va_end(ap1);
        }
    /*    void COUT(const char* fmt, ...)
        {
            ofstream out("output-file.txt");
            std::cout << "Cout to file";
            out << "Cout to file";
        }*/
        int main (int argc, char *argv[]) {

            FILE *outfile;
            char *mode = "a+";
            char outputFilename[] = "PRINT.log";
            outfile = fopen(outputFilename, mode);

            char bigfoot[] = "Hello 

World!\n";
        int howbad = 10;

        printf(outfile, "\n--------\n");
        //myout();

        /* then i realized that i can't send the arguments to fn:PRINTs */
        printf(outfile, "%s %i",bigfoot, howbad); /* error here! I can't send bigfoot and howbad*/

        system("pause");
        return 0;
    }

我已经在 COUT(大写,上面代码的注释部分)中完成了它。但是我想使用普通的 std::cout,那么我该如何覆盖它呢?并且它应该对 字符串和变量 都有效。

int i = 5;
cout << "Hello world" << i <<endl;

或者有没有办法捕获 stdout 数据,以便它们可以轻松地写入 文件和控制台


@nijansen:确实非常邪恶。 - Bathsheba
@nijansen:胡说八道,良好定义的机制:.rbduf() - MSalters
@nijansen 你可以直接在 main 函数中替换 cout 缓冲区。是的,这仍然不能解决每个在 main 函数之前初始化的对象的问题,但也许这不是一个问题。 - RedX
“error” 这个注释还有效吗?它似乎与您当前的问题无关。 - default
“printf”的代码是未定义行为。您不允许重新定义标准库中的函数。 - James Kanze
请参考此答案以获取重复问题的解决方案。 - Basile Starynkevitch
9个回答

14

1
确实很简单。在这种情况下,您只需要一个 “tee” streambuf,它将其输出复制到两个底层的 streambuf。 - MSalters

5

您可以交换底层缓冲区。下面是通过RAII实现的方法。

#include <streambuf>

class buffer_restore
{
    std::ostream&   os;
    std::streambuf* buf;
public:
    buffer_restore(std::ostream& os) : os(os), buf(os.rdbuf())
    { }

    ~buffer_restore()
    {
        os.rdbuf(buf);
    }
};

int main()
{
    buffer_restore b(std::cout);
    std::ofstream file("file.txt");

    std::cout.rdbuf(file.rdbuf());
    // ...
}

1
这个功能非常好用,可以捕获std::cout,因此我可以为一个大型现有程序构建ncurses界面。谢谢! - Joe Beuckman

2

覆盖std::cout的行为是一个非常糟糕的想法,因为其他开发人员将很难理解std::cout的使用不像通常那样。

通过简单的类来明确你的意图。

#include <fstream>
#include <iostream>

class DualStream
{
   std::ofstream file_stream;
   bool valid_state;
   public:
      DualStream(const char* filename) // the ofstream needs a path
      :
         file_stream(filename),  // open the file stream
         valid_state(file_stream) // set the state of the DualStream according to the state of the ofstream
      {
      }
      explicit operator bool() const
      {
         return valid_state;
      }
      template <typename T>
      DualStream& operator<<(T&& t) // provide a generic operator<<
      {
         if ( !valid_state ) // if it previously was in a bad state, don't try anything
         {
            return *this;
         }
         if ( !(std::cout << t) ) // to console!
         {
            valid_state = false;
            return *this;
         }
         if ( !(file_stream << t) ) // to file!
         {
            valid_state = false;
            return *this;
         }
         return *this;
      }
};
// let's test it:
int main()
{
   DualStream ds("testfile");
   if ( (ds << 1 << "\n" << 2 << "\n") )
   {
      std::cerr << "all went fine\n";
   }
   else
   {
      std::cerr << "bad bad stream\n";
   }
}

这提供了一个干净的接口,并且同时输出到控制台和文件中。 您可能想要添加一个flush方法或以追加模式打开文件。


不好的想法。两个流之间的格式保留在两个不同的流对象中,这可能会失去同步。实际上很容易,因为 std::cout 对象仍然可以在您的类外部访问。此外,您的流不是 ostream,因此无法传递给期望它的函数。最后,我认为您甚至不能在您的流上使用 std::endl - MSalters
@MSalters 我只提供了一个最小的工作实现。当然可以从ostream继承,你的第二个观点就不存在了。我讨厌使用std::endl,但复制那种行为也不是很难。不修改std::cout是我的答案的全部意义所在。 - stefan
这里的问题是“如何做”,而不是“我应该做吗”。在回答具体问题之后,您应该提供有用的(在某些情况下)建议。 - HAL9000

1
我假设您有一些使用std::coutprintf的代码,您不能修改它,否则解决问题的最简单方法是从cout写入不同的流,并使用fprintf而不是或与printf一起使用。
通过遵循这种方法,您可以定义一个新的流类,该类实际上将内容写入标准输出和给定的文件,并定义一个组合调用printffprintf的函数。
然而,一个更简单的方法是使用UNIX中最初的tee程序,该程序将其输入同时复制到输出和给定的文件。使用它,您可以以以下方式简单地调用程序:
your_program | tee your_log_file

这个问题的答案提供了一些Windows上可用的替代实现。个人建议在我的PC上安装cygwin,以便使用UNIX/Linux工具。


你说得对。在所有这些代码的最上方,我混用了printfcout。而且我还使用了第三方库g-test和g-mock,在其中无法将所有的cout重定向到用户定义的方式。那我应该怎么做呢? - Rasmi Ranjan Nayak
如果您的第三方库使用相同的C++库,则它共享相同的std::cout对象。在这种情况下,not-rightfold的答案使用std::cout.rd_buf将起作用,因为它会更改单个std::cout对象。如果该库使用printf,则可能会遇到更大的问题。 - MSalters

0

std::cout 写入到 stdout 文件,你可以在 Linux 和 Windows 上执行以下操作

#include <stdio.h>
#include <iostream>



int main()
{
    freopen("test.txt", "w", stdout);
    std::cout << "Hello strange stdout\n";
}

要将其改回,请使用从此处获取的以下内容

#include <stdio.h>
#include <stdlib.h>

void main(void)
{
   FILE *stream ;
   if((stream = freopen("file.txt", "w", stdout)) == NULL)
      exit(-1);

   printf("this is stdout output\n");

   stream = freopen("CON", "w", stdout);

   printf("And now back to the console once again\n");
}

注意:后者仅适用于Windows操作系统。


0

如果我猜得没错,您想把所有输出记录到文件中。

您需要的是观察者模式

将所有直接在代码中记录日志的操作替换为对新继电器的调用。 日志继电器将您的消息发送给观察者。 其中一个观察者将消息记录到屏幕上。 另一个观察者将消息记录到文件中。 尽可能避免使您的继电器成为单例。

此建议仅适用于您可以编辑所有源文件的情况。


-1
尝试使用宏 - 类似这样的东西(您需要添加包含):
#define MY_COUT(theos,printThis) { cout << printThis ; theos <<  printThis; }

void test()
{
     ofstream myos;
    myos.open("testfile", ios::trunc|ios::out);
    int i = 7;
    MY_COUT(myos, "try this numbers" << i << i + 1 << endl);
    myos.close()
}

正确的方法是使用输出运算符或函数(如果需要使用流对象(不仅仅是cout),并且需要设置自定义输出缓冲区,特别是需要使用cout)。在这里没有使用宏的理由。 - utnapistim

-1

cout通常被实现为一个对象实例,因此您无法像重载/覆盖函数或类一样覆盖它。

你最好不要去争论这个问题 - 是的,你可以构建一个my_cout#define cout my_cout,但那会使你的代码变得晦涩难懂。

为了可读性,我建议将cout保留为原样。它是一个标准,每个人都知道它能做什么和不能做什么。


1
-1;not-rightfold的答案可能不是最详细的,但这就是做法。 - Filip Roséen - refp

-1

已经有一个相关的 Boost 类:tee


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