C ++自定义stream操纵器,用于更改stream中的下一个项目

在C ++中,要以hex打印数字,请执行以下操作:

int num = 10; std::cout << std::hex << num; // => 'a' 

我知道我可以创build一个操纵器,只是像这样添加东西到stream中:

 std::ostream& windows_feed(std::ostream& out) { out << "\r\n"; return out; } std::cout << "Hello" << windows_feed; // => "Hello\r\n" 

但是,我怎样才能创build一个像“hex”一样的操作符来修改要传入的数据呢? 举一个简单的例子,我将如何在这里创buildplusone操纵器?

 int num2 = 1; std::cout << "1 + 1 = " << plusone << num2; // => "1 + 1 = 2" // note that the value stored in num2 does not change, just its display above. std::cout << num2; // => "1" 

首先,你必须在每个stream中存储一些状态。 您可以使用函数iword和由xalloc给出的索引传递给它:

 inline int geti() { static int i = ios_base::xalloc(); return i; } ostream& add_one(ostream& os) { os.iword(geti()) = 1; return os; } ostream& add_none(ostream& os) { os.iword(geti()) = 0; return os; } 

有了这个,你已经可以在所有的stream中获取一些状态。 现在,你只需要钩入相应的输出操作。 数字输出由一个方面完成,因为它可能是依赖于语言环境的。 所以你可以做

 struct my_num_put : num_put<char> { iter_type do_put(iter_type s, ios_base& f, char_type fill, long v) const { return num_put<char>::do_put(s, f, fill, v + f.iword(geti())); } iter_type do_put(iter_type s, ios_base& f, char_type fill, unsigned long v) const { return num_put<char>::do_put(s, f, fill, v + f.iword(geti())); } }; 

现在,你可以testing的东西。

 int main() { // outputs: 11121011 cout.imbue(locale(locale(),new my_num_put)); cout << add_one << 10 << 11 << add_none << 10 << 11; } 

如果您希望只增加下一个数字,则在每次调用do_put之后,再次将该字设置为0

我完全同意尼尔·巴特沃斯(Neil Butterworth)的观点,但是在具体的情况下,你使用的是这种可怕的黑客行为。 不要在任何生产代码中这样做。 它有很多的错误。 一方面,它只能在上面的单行工作,它不会改变基础stream的状态。

 class plusone_stream : public std::ostream { public: std::ostream operator<<(int i) { _out << i+1; return *this; } }; std::ostream& plusone(std::ostream& out) { return plusone_stream(out); } 

我为您的testing案例创build了一个简单的解决scheme,而不使用<iomanip> 。 我不能保证,同样的方法在现实生活中将起作用。

基本的方法是cout << plusone返回一个临时的辅助对象( PlusOnePlus ),这个对象又有一个重载的operator <<来执行加法操作。

我在Windows上testing过它:

 PlusOne plusone; cout << plusone << 41 

产生“42”,如预期的那样。 代码如下:

 class PlusOnePlus { public: PlusOnePlus(ostream& os) : m_os(os) {} // NOTE: This implementation relies on the default copy ctor, // assignment, etc. private: friend ostream& operator << (PlusOnePlus& p, int n); ostream& m_os; }; class PlusOne { public: static void test(ostream& os); }; PlusOnePlus operator << (ostream& os, const PlusOne p) { return PlusOnePlus(os); } ostream& operator << (PlusOnePlus& p, int n) { return p.m_os << n + 1; } void PlusOne::test(ostream& os) { PlusOne plusone; os << plusone << 0 << endl; os << plusone << 41 << endl; } 

编辑:评论代码指出,我依靠PlusOnePlus的默认复制构造函数(等)。 一个强大的实现可能会定义这些

你必须玩streamstates。 我已经为此主题添加了以下链接:

  • 关于提升ML的讨论
  • Maciej Sobczak关于CUJ / DDJ的文章

由于Maciej Sobczak图书馆不再在线提供,并且许可证允许我这样做(如果我错了,请纠正我的错误),下面是我从遗忘中拯救出来的主要文件的副本:

 // streamstate.h // // Copyright (C) Maciej Sobczak, 2002, 2003 // // Permission to copy, use, modify, sell and distribute this software is // granted provided this copyright notice appears in all copies. This software // is provided "as is" without express or implied warranty, and with no claim // as to its suitability for any purpose. // // <http://lists.boost.org/Archives/boost/2002/10/38275.php> // <http://www.ddj.com/dept/cpp/184402062?pgno=1> // <http://www.msobczak.com/prog/publications.html> #ifndef STREAMSTATE_H_INCLUDED #define STREAMSTATE_H_INCLUDED #include <ios> #include <istream> #include <ostream> // helper exception class, thrown when the source of error // was in one of the functions managing the additional state storage class StreamStateException : public std::ios_base::failure { public: explicit StreamStateException() : std::ios_base::failure( "Error while managing additional IOStream state.") { } }; // State should be: // default-constructible // copy-constructible // assignable // note: the "void *" slot is used for storing the actual value // the "long" slot is used to propagate the error flag template < class State, class charT = char, class traits = std::char_traits<charT> > class streamstate { public: // construct with the default state value streamstate() {} // construct with the given stream value streamstate(const State &s) : state_(s) {} // modifies the stream std::basic_ios<charT, traits> & modify(std::basic_ios<charT, traits> &ios) const { long *errslot; void *&p = state_slot(ios, errslot); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // here, do-nothing-in-case-of-error semantics if (ios.bad()) return ios; if (p == NULL) { // copy existing state object if this is new slot p = new State(state_); ios.register_callback(state_callback, 0); } else *static_cast<State*>(p) = state_; return ios; } // gets the current (possibly default) state from the slot static State & get(std::basic_ios<charT, traits> &ios) { long *errslot; void *&p = state_slot(ios, errslot); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // this function returns a reference and therefore // the only sensible error reporting is via exception if (ios.bad()) throw StreamStateException(); if (p == NULL) { // create default state if this is new slot p = new State; ios.register_callback(state_callback, 0); } return *static_cast<State*>(p); } private: // manages the destruction and format copying // (in the latter case performs deep copy of the state) static void state_callback(std::ios_base::event e, std::ios_base &ios, int) { long *errslot; if (e == std::ios_base::erase_event) { // safe delete if state_slot fails delete static_cast<State*>(state_slot(ios, errslot)); } else if (e == std::ios_base::copyfmt_event) { void *& p = state_slot(ios, errslot); State *old = static_cast<State*>(p); // Standard forbids any exceptions from callbacks try { // in-place deep copy p = new State(*old); } catch (...) { // clean the value slot and // set the error flag in the error slot p = NULL; *errslot = std::ios_base::badbit; } } } // returns the references to associated slot static void *& state_slot(std::ios_base &ios, long *&errslot) { static int index = std::ios_base::xalloc(); void *&p = ios.pword(index); errslot = &(ios.iword(index)); // note: if pword failed, // then p is a valid void *& initialized to 0 // (27.4.2.5/5) return p; } State state_; }; // partial specialization for iword functionality template < class charT, class traits > class streamstate<long, charT, traits> { public: // construct with the default state value streamstate() {} // construct with the given stream value streamstate(long s) : state_(s) {} // modifies the stream // the return value is not really useful, // it has to be downcasted to the expected stream type std::basic_ios<charT, traits> & modify(std::basic_ios<charT, traits> &ios) const { long &s = state_slot(ios); s = state_; return ios; } static long & get(std::basic_ios<charT, traits> &ios) { return state_slot(ios); } private: static long & state_slot(std::basic_ios<charT, traits> &ios) { static int index = std::ios_base::xalloc(); long &s = ios.iword(index); // this function returns a reference and we decide // to report errors via exceptions if (ios.bad()) throw StreamStateException(); return s; } long state_; }; // convenience inserter for ostream classes template < class State, class charT, class traits > std::basic_ostream<charT, traits> & operator<<(std::basic_ostream<charT, traits> &os, const streamstate<State> &s) { s.modify(os); return os; } // convenience extractor for istream classes template < class State, class charT, class traits > std::basic_istream<charT, traits> & operator>>(std::basic_istream<charT, traits> &is, const streamstate<State> &s) { s.modify(is); return is; } // the alternative if there is a need to have // many different state values of the same type // here, the instance of streamstate_value encapsulates // the access information (the slot index) template < class State, class charT = char, class traits = std::char_traits<char> > class streamstate_value { public: streamstate_value() : index_(-1) { } // returns a reference to current (possibly default) state State & get(std::basic_ios<charT, traits> &ios) { long *errslot; void *&p = state_slot(ios, errslot, index_); // propagate the error flag to the real stream state if (*errslot == std::ios_base::badbit) { ios.setstate(std::ios_base::badbit); *errslot = 0; } // this function returns a reference and the only // sensible way of error reporting is via exception if (ios.bad()) throw StreamStateException(); if (p == NULL) { // create default state if this is new slot p = new State; ios.register_callback(state_callback, index_); } return *static_cast<State*>(p); } private: // manages the destruction and format copying // (in the latter case performs deep copy of the state) static void state_callback(std::ios_base::event e, std::ios_base &ios, int index) { long *errslot; if (e == std::ios_base::erase_event) { // safe delete if state_slot fails delete static_cast<State*>(state_slot(ios, errslot, index)); } else if (e == std::ios_base::copyfmt_event) { void *& p = state_slot(ios, errslot, index); State *old = static_cast<State*>(p); // Standard forbids any exceptions from callbacks try { // in-place deep copy p = new State(*old); } catch (...) { // clean the value slot and set the error flag // in the error slot p = NULL; *errslot = std::ios_base::badbit; } } } // returns the references to associated slot static void *& state_slot(std::ios_base &ios, long *& errslot, int & index) { if (index < 0) { // first index usage index = std::ios_base::xalloc(); } void *&p = ios.pword(index); errslot = &(ios.iword(index)); // note: if pword failed, // then p is a valid void *& initialized to 0 // (27.4.2.5/5) return p; } int index_; }; // partial specialization for iword functionality template < class charT, class traits > class streamstate_value<long, charT, traits> { public: // construct with the default state value streamstate_value() : index_(-1) { } long & get(std::basic_ios<charT, traits> &ios) { if (index_ < 0) { // first index usage index_ = std::ios_base::xalloc(); } long &s = ios.iword(index_); if (ios.bad()) throw StreamStateException(); return s; } private: long index_; }; #endif // STREAMSTATE_H_INCLUDED 

litb的方法是“正确的方法”,对于复杂的东西来说是必要的,但是这样的东西可以足够好。 添加隐私和友谊的味道。

 struct PlusOne { PlusOne(int i) : i_(i) { } int i_; }; std::ostream & operator<<(std::ostream &o, const PlusOne &po) { return o << (po.i_ + 1); } std::cout << "1 + 1 = " << PlusOne(num2); // => "1 + 1 = 2" 

在这个简单的例子中,创build和stream式传输临时对象似乎比定义一个函数plusOne()更有帮助。 但是,假设你想这样工作:

 std::ostream & operator<<(std::ostream &o, const PlusOne &po) { return o << po.i_ << " + 1 = " << (po.i_ + 1); } std::cout << PlusOne(num2); // => "1 + 1 = 2" 

这不是对你的问题的直接回答,但是你不认为使用一个普通的旧函数比编写一个完整的操纵器更简单,更易于使用吗?

 #include <sstream> template<typename T> std::string plusone(T const& t) { std::ostringstream oss; oss << (t + 1); return oss.str(); } 

用法:

 cout << plusone(42); 

通过“清楚使用”,我的意思是用户不需要问自己,“它只影响下一个项目,还是所有后续项目?” 从检查中可以明显看出,只有函数的参数才会受到影响。

(对于plusone()例子,你可以通过返回一个T来进一步简化,但是返回一个std::string服务于一般情况。

hexdecdec操纵器只是改变现有streambasefield属性。

有关这些操纵器的更多信息,请参阅C ++参考手册。

正如尼尔·巴特沃斯(Neil Butterworth)所回答的 ,你需要扩展现有的stream类,或者创build自己的stream类,以便让影响将来值的操纵符插入到stream中。

plusone操纵器的例子中,stream对象必须有一个内部标志来指示应该将一个值添加到所有插入的值中。 plusone操纵器将简单地设置该标志,并且处理stream插入的代码将在插入数字之前检查该标志。