【问题标题】:Managing multiple stream operators (operator<<, >>) for same object为同一对象管理多个流运算符(运算符<<,>>)
【发布时间】:2013-05-02 09:51:43
【问题描述】:

我希望能够为一个对象定义流运算符(&lt;&lt;&gt;&gt;),但要拥有多个它们的集合,以便我可以根据上下文使用不同的集合。例如,如果我有一个对象如下:

struct Foo {
  int a;
};

然后在某些地方我希望能够使用这个

std::ostream& operator<<(std::ostream& out, const Foo& foo) {
  out<<foo.a;
}

在其他情况下我更喜欢使用这个

std::ostream& operator<<(std::ostream& out, const Foo& foo) {
  out<<"'"<<foo.a<<"'";
}

但我看不出如何同时定义这两个。

我正在提交我自己的答案,但我希望其他人过去有理由比我更仔细地考虑这一点,并且最好在大型应用程序中以一种有组织的方式来做这件事(因此术语“管理”在问题标题中)。

【问题讨论】:

    标签: c++ namespaces operator-overloading


    【解决方案1】:

    创建一个操纵器:一个简单的代理,它只为替代格式提供一个名称(和一个重载槽)。

    // default format
    std::ostream& operator<<(std::ostream& out, const Foo& foo) {
      return out<<foo.a;
    }
    
    // custom format
    
    // helper class
    struct print_quoted_manip {
        Foo const &ref;
    
        print_quoted_manip( Foo const &in )
            : ref( in ) {}
    };
    
    // implementation
    std::ostream& operator<<( std::ostream& out, print_quoted_manip o )
        { return out << '\'' << o.ref << '\''; }
    
    // the manipulator
    print_quoted_manip print_quoted( Foo const &in )
        { return print_quoted_manip( in ); }
    
    // usage
    std::cout << print_quoted( my_foo );
    

    您可以轻松地对其进行模板化,以便在任何类型的对象周围加上引号。

    【讨论】:

      【解决方案2】:

      虽然您提出的解决方案可行,但我强烈建议您不要这样做。运算符重载是一个很容易被误用的强大工具。如果行为不是直观和明显,则通常弊大于利。

      我建议只使用第一个版本

      std::ostream& operator<<(std::ostream& out, const Foo& foo) {
        out<<foo.a;
      }
      

      简单地使用

      cout << "'" << fooObject << "'";
      

      而不是提供不同的重载。

      这使代码清晰直观,并且更易于调试。

      我可以想象在代码中不当使用using namespace 会产生许多错误。还有代码重复 - 如果您想将另一个成员 b 添加到对象并打印该成员怎么办。其他人可能会找到其中一个重载,对其进行修改,第一眼看到它可以工作,然后忘记另一个重载。

      【讨论】:

      • 简单地用引号括起来只是我在这里使用的一个简单示例(也许太简单了?)。假设不同的上下文需要截然不同的输出字符串,因此您不能在其中一个中重用相同的运算符定义,例如带有命名字段的详细调试语句:(a=1, b=2) vs 逗号分隔的值列表:[1,2]
      • @Benedict 我仍然看不到多个重载是直观的用例。
      【解决方案3】:

      您可以围绕每组运算符声明命名空间,并适当命名,然后在需要使用这些特定流运算符的函数中使用该命名空间。例如:

      #include <iostream>
      #include <string>
      
      struct Foo {
        int a;
      };
      
      namespace raw_output {
        std::ostream& operator<<(std::ostream& out, const Foo& foo) {
            out<<foo.a;
        }
      }
      
      namespace quoted_output {
        std::ostream& operator<<(std::ostream& out, const Foo& foo) {
            out<<"'"<<foo.a<<"'";
        }
      }
      
      void do_raw() {
        using namespace raw_output;
        Foo f = { 7 };
        std::cout<<f<<std::endl;
      }
      
      void do_quoted() {
        using namespace quoted_output;
        Foo f = { 7 };
        std::cout<<f<<std::endl;
      }
      
      void do_raw_and_quoted() {
        Foo f = { 7 };
        { // Anonymous scoping to use different ops in the same function scope
          using namespace raw_output;
          std::cout<<f<<std::endl;
        }
        {
          using namespace quoted_output;
          std::cout<<f<<std::endl;
        }
      }
      
      int main() {
        do_raw();
        do_quoted();
        do_raw_and_quoted();
      }
      

      使用这种方法,您应该避免在顶层(函数范围之外)使用命名空间,因为这可能会污染所需的运算符并使使用不明确。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多