【问题标题】:C++ custom stream manipulator that changes next item on streamC++ 自定义流操纵器,可更改流中的下一项
【发布时间】:2010-10-22 10:12:43
【问题描述】:

在 C++ 中,要以十六进制打印数字,请执行以下操作:

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

我知道我可以创建一个只向流中添加内容的操纵器,如下所示:

std::ostream& windows_feed(std::ostream& out)
{
    out << "\r\n";
    return out;
}

std::cout << "Hello" << windows_feed; // => "Hello\r\n"

但是,如何创建一个像“hex”一样修改项目以进入流的操纵器?举个简单的例子,我将如何在这里创建 plusone 操纵器?:

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"

【问题讨论】:

标签: c++ stream


【解决方案1】:

首先,您必须将一些状态存储到每个流中。您可以使用函数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; }

有了这些,您就可以在所有流中检索一些状态。现在,您只需要挂钩相应的输出操作。数字输出由一个方面完成,因为它可能与语言环境有关。所以你可以做

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())); 
    } 
}; 

现在,您可以测试这些东西了。

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

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

【讨论】:

  • +1。这是彻底的,按书本(如您所见,相当复杂)的解决方案。但是我想知道是否只创建一个函数 plusone() 并返回一个参数并返回结果会不会更简单(并且可能更清晰)?
  • @Apollys 当分配给它的locale 被破坏时。
  • 所以,等一下。 “static int i = ios_base::xalloc();”是怎么回事?每次调用 geti() 时不会调用 xalloc() 吗?我正在尝试这个,我看到 xalloc() 每个调用站点返回相同的唯一值,而不是每个动态调用。这是如何运作的?我看到了一个内部锁定的 xalloc() 实现,因此它正确地从全局命名空间分配唯一编号,但是是什么让它每次从调用站点返回相同的索引?
  • @AlltheRage 因为它是static,因此只初始化一次(在第一次调用geti 之前)。所有后续调用都返回存储的值。
【解决方案2】:

我完全同意 Neil Butterworth 在这一点上的看法,但是在您使用的特定情况下,您可以执行这种完全可怕的 hack。不要在任何生产代码中这样做。它有很多错误。一方面它只适用于你上面的单行,它不会改变底层流的状态。

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);
}

【讨论】:

    【解决方案3】:

    这不是你问题的直接答案,但你不认为使用一个普通的旧函数比编写一个完整的操纵器更容易实现和使用更清晰吗?

    #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 适用于一般情况。)

    【讨论】:

      【解决方案4】:

      我为您的测试用例创建了一个简单的解决方案,而没有使用&lt;iomanip&gt;。我不能保证同样的方法会在现实生活中奏效。

      基本方法是cout &lt;&lt; plusone返回一个临时辅助对象(PlusOnePlus),该对象又具有执行加法的重载operator &lt;&lt;

      我已经在 Windows 上测试过了:

      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 的默认复制构造函数(等)。一个健壮的实现可能会定义这些

      【讨论】:

      • 聪明,但请注意以下行不通:"os
      • 我可以说这是一个特性 :-) 虽然它与机械手操作的标准方式不一致。
      • 是的,我怀疑有人会注意到 C++ 中还有一个更无端的不一致... ;) +1。
      【解决方案5】:

      您必须使用流状态。我已为该主题的以下链接添加了书签:

      由于 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 
      

      【讨论】:

      • 图书馆链接已失效。
      • @BenFulton。确实。不幸的是,图书馆似乎不再可用。 IIRC,我在某处有一份 zip 副本。就在这里。不过,现在回退到 boost 可能会更好。
      【解决方案6】:

      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"
      

      在这个简单的示例中,创建和流式传输一个临时对象似乎并没有像有人建议的那样定义一个函数 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"
      

      【讨论】:

        【解决方案7】:

        hexdecoct 操纵器只需更改现有 streambasefield 属性。

        有关这些操纵器的更多详细信息,请参阅C++ Reference

        正如Neil Butterworth's answer 中所发布的,您需要扩展现有的流类,或创建自己的类,以便将影响未来值的操纵器插入到流中。

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

        【讨论】:

        • 抱歉 -1 - iostream 确实包含 litb 所指出的可扩展性机制(xalloc()、iword()、pword())。
        • 无需道歉。我显然错了,如果没有看到-1,我就不会知道。感谢您引起我的注意!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-11
        • 1970-01-01
        • 2014-12-30
        • 1970-01-01
        • 2020-02-22
        相关资源
        最近更新 更多