jjzjj

c++ - 如何组合输出流,以便同时输出多个位置?

coder 2024-02-26 原文

我想将两个(或多个)流合二为一。我的目标是将指向 coutcerrclog 的任何输出连同原始流一起输出到文件中。 (例如,当事情被记录到控制台时。关闭后,我希望仍然能够返回并查看输出。)

我正在考虑做这样的事情:

class stream_compose : public streambuf, private boost::noncopyable
{
public:
    // take two streams, save them in stream_holder,
    // this set their buffers to `this`.
    stream_compose;

    // implement the streambuf interface, routing to both
    // ...

private:
    // saves the streambuf of an ios class,
    // upon destruction restores it, provides
    // accessor to saved stream
    class stream_holder;

    stream_holder mStreamA;
    stream_holder mStreamB;
};

这看起来很简单。 main 中的调用将类似于:

// anything that goes to cout goes to both cout and the file
stream_compose coutToFile(std::cout, theFile);
// and so on

我也查看了 boost::iostreams,但没有看到任何相关内容。

有没有其他更好/更简单的方法来完成这个?

最佳答案

如果你想纯粹在 stdlib 中做这件事,你的设计确实是正确的。

有一件事:不是在每个输出上都连接到每个流缓冲区,而是将其实现为使用与给定的流缓冲区之一相同的放置区域,并在溢出和同步时复制到其他流缓冲区。这将最大限度地减少虚拟调用,这是流缓冲区工作方式的目标之一。

或者,如果您只想处理 stdout 和 stderr(这很常见),请通过标准 Unix tee 程序(或您平台上的等效程序)运行您的程序,方法是您自己在调用程序时,或在程序内通过 fork 、适当设置流等。

编辑:您让我开始思考,我应该知道如何做对了。这是我的 first approximation . (当这打破时,你可以保留两件。)

#ifndef INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676
#define INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676

#include <cassert>
#include <cstring>
#include <streambuf>
#include <map>
#include <vector>

template<class CharT, class Traits=std::char_traits<CharT> >
struct basic_streamtee : std::basic_streambuf<CharT, Traits> {
    typedef std::basic_ios<CharT, Traits> Stream;
    typedef std::basic_streambuf<CharT, Traits> StreamBuf;

    typedef typename StreamBuf::char_type char_type;
    typedef typename StreamBuf::traits_type traits_type;
    typedef typename StreamBuf::int_type int_type;
    typedef typename StreamBuf::pos_type pos_type;
    typedef typename StreamBuf::off_type off_type;

    basic_streamtee() : _key_buf(0) {}
    basic_streamtee(Stream& a, Stream& b) : _key_buf(0) {
        this->pubimbue(a.rdbuf()->getloc());
        _set_key_buf(a.rdbuf());
        insert(a);
        insert(b);
    }
    ~basic_streamtee() {
        sync();
        for (typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.begin();
             i != _bufs.end();
             ++i)
        {
            StreamBuf* old = i->first->rdbuf(i->second);
            if (old != this) {
                old->pubsync();
            }
        }
    }

    // add this functionality?
    // streambufs would be unconnected with a stream
    // easy to do by changing _bufs to a multimap
    // and using null pointers for the keys
    //void insert(StreamBuf* buf);
    //void remove(StreamBuf* buf);

    void insert(Stream& s) {
        sync();
        if (!_bufs.count(&s)) {
            if (!_key_buf) {
                _set_key_buf(s.rdbuf());
            }
            _bufs[&s] = s.rdbuf(this);
        }
    }
    void remove(Stream& s) {
        sync();
        typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.find(&s);
        if (i != _bufs.end()) {
            StreamBuf* old = i->second;
            i->first->rdbuf(i->second);
            _bufs.erase(i);

            if (old == _key_buf) {
                _set_key_buf(_bufs.empty() ? 0 : _bufs.begin()->second);
            }
        }
    }

private:
    basic_streamtee(basic_streamtee const&); // not defined
    basic_streamtee& operator=(basic_streamtee const&); // not defined

    StreamBuf* _key_buf;
    std::map<Stream*, StreamBuf*> _bufs;

    void _set_key_buf(StreamBuf* p) {
        //NOTE: does not sync, requires synced already
        _key_buf = p;
        _update_put_area();
    }
    void _update_put_area() {
        //NOTE: does not sync, requires synced already
        if (!_key_buf) {
            this->setp(0, 0);
        }
        else {
            this->setp((_key_buf->*&basic_streamtee::pbase)(),
                       (_key_buf->*&basic_streamtee::epptr)());
        }
    }


#define FOREACH_BUF(var) \
for (typename std::map<Stream*, StreamBuf*>::iterator var = _bufs.begin(); \
var != _bufs.end(); ++var)


    // 27.5.2.4.1 Locales
    virtual void imbue(std::locale const& loc) {
        FOREACH_BUF(iter) {
            iter->second->pubimbue(loc);
        }
    }


    // 27.5.2.4.2 Buffer management and positioning
    //virtual StreamBuf* setbuf(char_type* s, std::streamsize n); // not required
    //virtual pos_type seekoff(off_type off, std::ios_base::seekdir way,
    //                         std::ios_base::openmode which); // not required
    //virtual pos_type seekpos(pos_type sp, std::ios_base::openmode which); // not required
    virtual int sync() {
        if (!_key_buf) {
            return -1;
        }
        char_type* data = this->pbase();
        std::streamsize n = this->pptr() - data;
        (_key_buf->*&basic_streamtee::pbump)(n);
        FOREACH_BUF(iter) {
            StreamBuf* buf = iter->second;
            if (buf != _key_buf) {
                buf->sputn(data, n); //BUG: ignores put errors
                buf->pubsync(); //BUG: ignroes errors
            }
        }
        _key_buf->pubsync(); //BUG: ignores errors
        _update_put_area();
        return 0;
    }


    // 27.5.2.4.3 Get area
    // ignore input completely, teeing doesn't make sense
    //virtual std::streamsize showmanyc();
    //virtual std::streamsize xsgetn(char_type* s, std::streamsize n);
    //virtual int_type underflow();
    //virtual int_type uflow();


    // 27.5.2.4.4 Putback
    // ignore input completely, teeing doesn't make sense
    //virtual int_type pbackfail(int_type c);


    // 27.5.2.4.5 Put area
    virtual std::streamsize xsputn(char_type const* s, std::streamsize n) {
        assert(n >= 0);
        if (!_key_buf) {
            return 0;
        }

        // available room in put area? delay sync if so
        if (this->epptr() - this->pptr() < n) {
            sync();
        }
        // enough room now?
        if (this->epptr() - this->pptr() >= n) {
            std::memcpy(this->pptr(), s, n);
            this->pbump(n);
        }
        else {
            FOREACH_BUF(iter) {
                iter->second->sputn(s, n);
                //BUG: ignores put errors
            }
            _update_put_area();
        }
        return n;
    }
    virtual int_type overflow(int_type c) {
        bool const c_is_eof = traits_type::eq_int_type(c, traits_type::eof());
        int_type const success = c_is_eof ? traits_type::not_eof(c) : c;
        sync();
        if (!c_is_eof) {
            char_type cc = traits_type::to_char_type(c);
            xsputn(&cc, 1);
            //BUG: ignores put errors
        }
        return success;
    }

#undef FOREACH_BUF
};

typedef basic_streamtee<char> streamtee;
typedef basic_streamtee<wchar_t> wstreamtee;

#endif

现在,这个测试还远未完成,但它似乎有效:

#include "streamtee.hpp"

#include <cassert>
#include <iostream>
#include <sstream>

int main() {
    using namespace std;
    {
        ostringstream a, b;
        streamtee tee(a, b);
        a << 42;
        assert(a.str() == "42");
        assert(b.str() == "42");
    }
    {
        ostringstream a, b;
        streamtee tee(cout, a);
        tee.insert(b);
        a << 42 << '\n';
        assert(a.str() == "42\n");
        assert(b.str() == "42\n");
    }
    return 0;
}

将其与文件放在一起:

#include "streamtee.hpp"

#include <iostream>
#include <fstream>

struct FileTee {
  FileTee(std::ostream& stream, char const* filename)
  : file(filename), buf(file, stream)
  {}

  std::ofstream file;
  streamtee buf;
};

int main() {
  using namespace std;

  FileTee out(cout, "stdout.txt");
  FileTee err(clog, "stderr.txt");
  streambuf* old_cerr = cerr.rdbuf(&err.buf);

  cout << "stdout\n";
  clog << "stderr\n";

  cerr.rdbuf(old_cerr);
  // watch exception safety

  return 0;
}

关于c++ - 如何组合输出流,以便同时输出多个位置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8220956/

有关c++ - 如何组合输出流,以便同时输出多个位置?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

随机推荐