std::cout 重定向到 QTextEdit中

std::cout重定向到QTextEdit的实现
本文讲述将std::cout重定向到QTextEdit的方法。因将控制台项目迁移到Qt,想把std::cout打印的日志输出到QTextEdit。先对libstdc++源码分析,了解std::cout实现,再仿照stdio_sync_filebuf声明类并重写相关函数,最后在MainWindow中操作实现重定向。

std::cout 重定向到 QTextEdit中

一、背景

最近把一个控制台运行的项目迁移到qt时,考虑到之前项目中有大量通过std::cout打印出的日志。所以想在迁移到qt时用TextEdit做一个日志输出栏,将原来std::cout打印出的日志输出到这里,效果见下图:
而且想对原有的代码影响尽量小,所以考虑是否可以通过改动代码,把std::cout的输出给重定向到QTextEdit中

二、libstdc++源码分析

背景中说的重定向这个词其实不是特别准确,正常的重定向其实是从输出到stdout改成输出到其他的文件描述符,而这里把std::cout的内容输出到QTextEdit,与其说是重定向,不如用拦截std::cout的输出过程描述更为恰当。所以需要看一下std::cout在源码中到底是如何实现的。
众所周知,std::cout的定义是在iostream里,该头文件极为精简:

#ifndef _GLIBCXX_IOSTREAM
#define _GLIBCXX_IOSTREAM 1

#pragma GCC system_header

#include <bits/c++config.h>
#include <ostream>
#include <istream>

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  /**
   *  @name Standard Stream Objects
   *
   *  The &lt;iostream&gt; header declares the eight <em>standard stream
   *  objects</em>.  For other declarations, see
   *  http://gcc.gnu.org/onlinedocs/libstdc++/manual/io.html
   *  and the @link iosfwd I/O forward declarations @endlink
   *
   *  They are required by default to cooperate with the global C
   *  library's @c FILE streams, and to be available during program
   *  startup and termination. For more information, see the section of the
   *  manual linked to above.
  */
  //@{
  extern istream cin;		/// Linked to standard input
  extern ostream cout;		/// Linked to standard output
  extern ostream cerr;		/// Linked to standard error (unbuffered)
  extern ostream clog;		/// Linked to standard error (buffered)

#ifdef _GLIBCXX_USE_WCHAR_T
  extern wistream wcin;		/// Linked to standard input
  extern wostream wcout;	/// Linked to standard output
  extern wostream wcerr;	/// Linked to standard error (unbuffered)
  extern wostream wclog;	/// Linked to standard error (buffered)
#endif
  //@}

  // For construction of filebuffers for cout, cin, cerr, clog et. al.
  static ios_base::Init __ioinit;

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

#endif /* _GLIBCXX_IOSTREAM */

可以看出iosteam中只是对std::cout做了一个extern的声明,具体如何去定义的还是去看操作系统链接的libstdc++中的实现。
以gcc中libstdc++的实现为例,std::cout在gcc/libstdc+±v3/src/c++98/ios_init.cc(链接)中进行初始化

ios_base::Init::Init()
  {
    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
      {
	// Standard streams default to synced with "C" operations.
	_S_synced_with_stdio = true;

	new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
	new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
	new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);

	// The standard streams are constructed once only and never
	// destroyed.
	new (&cout) ostream(&buf_cout_sync);
	new (&cin) istream(&buf_cin_sync);
	new (&cerr) ostream(&buf_cerr_sync);
	new (&clog) ostream(&buf_cerr_sync);
	}
	...
}

从源码上看,gcc其实是通一个stdio_sync_filebuf的对象,实现了一个通过文件描述符进行io的buffer,不论是std::cout还是std::cin,std::cerr,都通过对应的文件描述符与buffer绑定,然后通过这个buffer来构造ostream或者istream,所以这个buffer至关重要,他的定义位于gcc/libstdc+±v3/include/ext/stdio_sync_filebuf.h(链接),简化版的代码(删掉wchar支持和c++11特性)如下所示

#ifndef _STDIO_SYNC_FILEBUF_H
#define _STDIO_SYNC_FILEBUF_H 1

#pragma GCC system_header

#include <bits/requires_hosted.h> // GNU extensions are currently omitted

#include <streambuf>
#include <cstdio>
#include <bits/c++io.h>  // For __c_file
#include <bits/move.h>   // For __exchange、
namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  /**
   *  @brief Provides a layer of compatibility for C.
   *  @ingroup io
   *
   *  This GNU extension provides extensions for working with standard
   *  C FILE*'s.  It must be instantiated by the user with the type of
   *  character used in the file stream, e.g., stdio_filebuf<char>.
  */
  template<typename _CharT, typename _Traits = std::char_traits<_CharT> >
    class stdio_sync_filebuf : public std::basic_streambuf<_CharT, _Traits>
    {
    public:
      // Types:
      typedef _CharT					char_type;
      typedef _Traits					traits_type;
      typedef typename traits_type::int_type		int_type;
      typedef typename traits_type::pos_type		pos_type;
      typedef typename traits_type::off_type		off_type;

    private:
      typedef std::basic_streambuf<_CharT, _Traits> __streambuf_type;

      // Underlying stdio FILE
      std::__c_file* _M_file;

      // Last character gotten. This is used when pbackfail is
      // called from basic_streambuf::sungetc()
      int_type _M_unget_buf;

    public:
      explicit
      stdio_sync_filebuf(std::__c_file* __f)
      : _M_file(__f), _M_unget_buf(traits_type::eof())
      { }

      /**
       *  @return  The underlying FILE*.
       *
       *  This function can be used to access the underlying C file pointer.
       *  Note that there is no way for the library to track what you do
       *  with the file, so be careful.
       */
      std::__c_file*
      file() { return this->_M_file; }

    protected:
      int_type
      syncgetc();

      int_type
      syncungetc(int_type __c);

      int_type
      syncputc(int_type __c);

      virtual int_type
      underflow()
      {
	int_type __c = this->syncgetc();
	return this->syncungetc(__c);
      }

      virtual int_type
      uflow()
      {
	// Store the gotten character in case we need to unget it.
	_M_unget_buf = this->syncgetc();
	return _M_unget_buf;
      }

      virtual int_type
      pbackfail(int_type __c = traits_type::eof())
      {
	int_type __ret;
	const int_type __eof = traits_type::eof();

	// Check if the unget or putback was requested
	if (traits_type::eq_int_type(__c, __eof)) // unget
	  {
	    if (!traits_type::eq_int_type(_M_unget_buf, __eof))
	      __ret = this->syncungetc(_M_unget_buf);
	    else // buffer invalid, fail.
	      __ret = __eof;
	  }
	else // putback
	  __ret = this->syncungetc(__c);

	// The buffered character is no longer valid, discard it.
	_M_unget_buf = __eof;
	return __ret;
      }

      virtual std::streamsize
      xsgetn(char_type* __s, std::streamsize __n);

      virtual int_type
      overflow(int_type __c = traits_type::eof())
      {
	int_type __ret;
	if (traits_type::eq_int_type(__c, traits_type::eof()))
	  {
	    if (std::fflush(_M_file))
	      __ret = traits_type::eof();
	    else
	      __ret = traits_type::not_eof(__c);
	  }
	else
	  __ret = this->syncputc(__c);
	return __ret;
      }

      virtual std::streamsize
      xsputn(const char_type* __s, std::streamsize __n);

      virtual int
      sync()
      { return std::fflush(_M_file); }

      virtual std::streampos
      seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
	      std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
      {
	std::streampos __ret(std::streamoff(-1));
	int __whence;
	if (__dir == std::ios_base::beg)
	  __whence = SEEK_SET;
	else if (__dir == std::ios_base::cur)
	  __whence = SEEK_CUR;
	else
	  __whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
	if (!fseeko64(_M_file, __off, __whence))
	  __ret = std::streampos(ftello64(_M_file));
#else
	if (!fseek(_M_file, __off, __whence))
	  __ret = std::streampos(std::ftell(_M_file));
#endif
	return __ret;
      }

      virtual std::streampos
      seekpos(std::streampos __pos,
	      std::ios_base::openmode __mode =
	      std::ios_base::in | std::ios_base::out)
      { return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
    };

  template<>
    inline stdio_sync_filebuf<char>::int_type
    stdio_sync_filebuf<char>::syncgetc()
    { return std::getc(_M_file); }

  template<>
    inline stdio_sync_filebuf<char>::int_type
    stdio_sync_filebuf<char>::syncungetc(int_type __c)
    { return std::ungetc(__c, _M_file); }

  template<>
    inline stdio_sync_filebuf<char>::int_type
    stdio_sync_filebuf<char>::syncputc(int_type __c)
    { return std::putc(__c, _M_file); }

  template<>
    inline std::streamsize
    stdio_sync_filebuf<char>::xsgetn(char* __s, std::streamsize __n)
    {
      std::streamsize __ret = std::fread(__s, 1, __n, _M_file);
      if (__ret > 0)
	_M_unget_buf = traits_type::to_int_type(__s[__ret - 1]);
      else
	_M_unget_buf = traits_type::eof();
      return __ret;
    }

  template<>
    inline std::streamsize
    stdio_sync_filebuf<char>::xsputn(const char* __s, std::streamsize __n)
    { return std::fwrite(__s, 1, __n, _M_file); }

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

#endif

这个类继承自std::basic_streambuf,和output相关的函数只重写了overflow,xsputn,sync和seekoff,而seekoff和sync和本文的关联不大,所以不做赘述。xsputn的实现看上去十分简洁,用fwrite把buffer输出到标准输出对应的文件描述符中。而overflow的作用是给定一个字符,判断是否输出到了eof,顺带将这个字符输出,在gcc中的逻辑是:假如字符是eof,根据fflush的返回值判断是否返回eof,假如不是eof,直接用fputc打印当前字符。

三、改造std::cout

从上面的分析可知,可以仿照stdio_sync_filebuf的实现,声明一个类继承自std::basic_streambuf,并且重写xsputn和overflow,然后用上述的这个类重新构造std::cout,便达到可以拦截标准输出的目的。源码如下:
qtStreamBuf.h

#ifndef QTSTREAMBUF_H
#define QTSTREAMBUF_H

#include <streambuf>
#include <bits/c++io.h>  // For __c_file

class MainWindow;
using _CharT=char;
using _Traits = std::char_traits<_CharT>;
class qtStreamBuf : public std::basic_streambuf<_CharT, _Traits>
{
public:
    typedef _CharT char_type;
    typedef _Traits traits_type;
    typedef typename traits_type::int_type int_type;
private:
    MainWindow* window;
    int_type syncput(int_type c);
public:
      qtStreamBuf(MainWindow* window)
          : window(window)
      { }
protected:
      virtual int_type overflow(int_type __c = traits_type::eof())
      {
          int_type __ret;
          if (traits_type::eq_int_type(__c, traits_type::eof()))
          {
              __ret = traits_type::not_eof(__c);
          }
          else
              __ret = syncput(__c);
          return __ret;
       }
      virtual std::streamsize xsputn(const char_type* __s, std::streamsize __n);
    };

#endif

qtStreamBuf.cpp

#include "qtstreambuf.h"
#include "mainwindow.h"

std::streamsize qtStreamBuf::xsputn(const char* __s, std::streamsize __n)
{
    std::string s(__s,__n);
    window->Append(QString::fromStdString(s));
    return __n;
}
qtStreamBuf::int_type qtStreamBuf::syncput(qtStreamBuf::int_type c) {
    window->Append(QString(char(c)));
    return c;
}

此时,只需要在MainWindow中定义一个Append方法,把std::cout中的输出追加到QTextEdit中

void MainWindow::Append(const QString &text)
{
    static QString textbuf;
    textbuf+=text;
    ui->logText->setText(textbuf);
}

在MainWindow构造时hook一下std::cout,便可以将其重定向到QTextEdit中

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    buffer=std::make_shared<qtStreamBuf>(this);
    new (&std::cout) std::ostream(buffer.get());
    ....
}
``
C++中,`std::cout`是标准输出流,默认情况下将信息输出到控制台。通过使用`std::ofstream`,可以将`std::cout`的输出重定向到文件,从而将原本显示在控制台的信息写入到指定的文件中。实现这一功能的核心思想是利用`std::streambuf`对象来切换输出流的底层缓冲区。 具体实现步骤如下: 1. 创建一个`std::ofstream`对象,并将其关联到目标文件。例如: ```cpp std::ofstream out("output.txt"); ``` 这行代码创建了一个名为`output.txt`的文件流对象`out`,后续的输出操作将通过该对象进行。 2. 保存当前`std::cout`的缓冲区指针,以便在后续恢复原始输出流: ```cpp std::streambuf *coutbuf = std::cout.rdbuf(); ``` 这里调用了`std::cout.rdbuf()`函数,获取当前`std::cout`的缓冲区指针,并将其存储在变量`coutbuf`中。 3. 将`std::cout`的缓冲区指针替换为文件流的缓冲区指针,从而实现输出重定向: ```cpp std::cout.rdbuf(out.rdbuf()); ``` 通过将`std::cout`的缓冲区指针替换为`out`对象的缓冲区指针,所有后续的`std::cout`输出都将被写入到`output.txt`文件中。 4. 在完成重定向后,可以像平常一样使用`std::cout`进行输出操作,这些信息将被写入到文件中: ```cpp std::cout << "This will be written to the file 'output.txt'" << std::endl; ``` 5. 如果需要恢复`std::cout`的输出到控制台,可以将之前保存的缓冲区指针重新赋值给`std::cout`: ```cpp std::cout.rdbuf(coutbuf); ``` 这样,`std::cout`的输出流将恢复到原始状态,后续的输出操作将重新显示在控制台上。 6. 最后,可以再次使用`std::cout`进行控制台输出: ```cpp std::cout << "This will be written to the console" << std::endl; ``` 上述方法可以有效地将`std::cout`的输出重定向到文件,适用于需要将程序输出保存到文件的场景。需要注意的是,在完成重定向后,所有`std::cout`的输出都将被写入到文件中,直到恢复原始缓冲区指针为止。此外,重定向操作应在程序退出前正确恢复`std::cout`的缓冲区指针,以避免潜在的资源泄漏问题。 ### 示例代码 以下是一个完整的示例代码,展示了如何将`std::cout`的输出重定向到文件,并在完成操作后恢复到控制台: ```cpp #include <iostream> #include <fstream> int main() { // 创建文件流对象并关联到目标文件 std::ofstream out("output.txt"); // 保存当前std::cout的缓冲区指针 std::streambuf *coutbuf = std::cout.rdbuf(); // 将std::cout的缓冲区指针替换为文件流的缓冲区指针 std::cout.rdbuf(out.rdbuf()); // 输出信息到文件 std::cout << "This will be written to the file 'output.txt'" << std::endl; // 恢复std::cout的缓冲区指针 std::cout.rdbuf(coutbuf); // 输出信息到控制台 std::cout << "This will be written to the console" << std::endl; return 0; } ``` ### 注意事项 - 在使用`std::ofstream`进行重定向时,应确保文件路径正确且具有写入权限,否则可能导致文件打开失败。 - 重定向操作可能会影响程序的调试过程,因此建议在调试完成后使用此功能。 - 如果需要屏蔽`std::cout`的输出,可以将其重定向到`/dev/null`(在类Unix系统中)或等效的空设备[^3]。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值