std::cout 重定向到 QTextEdit中

目录

  • std::cout 重定向到 QTextEdit中
    • 一、背景
    • 二、libstdc++源码分析
    • 三、改造std::cout

std::cout 重定向到 QTextEdit中

一、背景

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

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  /**
   *  @name Standard Stream Objects
   *
   *  The <iostream> header declares the eight standard stream
   *  objects.  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  // GNU extensions are currently omitted

#include 
#include 
#include   // For __c_file
#include    // 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.
  */
  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 
#include   // 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++,qt,开发语言)