【问题标题】:When to Overload the Comma Operator?何时重载逗号运算符?
【发布时间】:2011-08-01 21:46:19
【问题描述】:

我经常看到关于 SO 的问题是关于在 C++ 中重载逗号运算符(主要与重载本身无关,但类似于序列点的概念),这让我想知道:

什么时候应该你超载逗号?有哪些实际用途的例子?

我只是想不出任何我见过或需要类似的例子

foo, bar;

在实际代码中,所以我很好奇何时(如果有的话)实际使用它。

【问题讨论】:

  • 既然 C++ 有统一的初始化语法,这些技术大部分都是不必要的。

标签: c++ function operator-overloading


【解决方案1】:

Boost.Assign 使用它,让您执行以下操作:

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

我已经看到它用于古怪的语言黑客,我会看看我能不能找到一些。


啊哈,我确实记得其中一个古怪的用法:collecting multiple expressions。 (警告,黑魔法。)

【讨论】:

  • 呃,找不到。 非常极端情况。
  • 但是说真的,你真的想写这样的代码吗?对于阅读您的代码的人来说,这将完全令人困惑。我假设 sn-p 是这 8 个值上 push_back 的简写,但它看起来 9 被添加到 vector&lt;int&gt; 中,这没有任何意义。坦率地说,这是对 Boost 作为“高质量库”的有力反驳。代码应该清晰明了。否则,也可以实现类似T&amp; operator--(int){ delete this; return *this; } 的东西,它也可能工作得很好。 发生了什么对其他人来说并不明显。
  • 嗯,operator+= 加,一般理解,右边表达式的值。在常识中,表达式 1,2,...9 的计算结果为 9。重载运算符颠覆了语义,虽然它在语法上是有效的,但这并不意味着它一定是好的。运算符重载如果可以使代码清晰,则很好,但在这里它会使代码模棱两可和令人困惑(至少在我看来)。这与例如有很大不同。 C++0x 中的 initializer_list 赋值,因为花括号可以立即看出发生了什么。另外,我认为重载 operator+= 为矢量...
  • ... 可能不是最明智的选择之一,因为该运算符在向量上至少有两个同样有效的解释。我假设“将元素附加到结尾”是这里的意思,但它同样可以是“使用这些参数在向量中的每个元素上调用运算符 +=”。它很可能只为大小相等的集合定义,或者它可能对较小的集合进行零扩展,或者其他什么......事情是,如果不深入研究文档,你就不会知道,这并不明显。好代码不用解释就一目了然。
  • 另一个例子,我记得几年前遇到过一个字符串类,它重载了operator&lt;=。这使您可以编写很酷的代码,例如str &lt;= "foo";。除非当下一个阅读您的代码的人说“这到底是什么?”时,这根本不酷。当你第一次因为不知道有人写了if(str &lt;= "bar") 之类的东西而花一周时间进行调试时,它变得完全不酷。
【解决方案2】:

让我们把重点改一下:

什么时候应该重载逗号?

答案:从不。

例外:如果您在进行模板元编程,operator, 在运算符优先级列表的最底部有一个特殊的位置,它可以在构造 SFINAE-guard 等时派上用场。

我见过的重载operator, 的唯一两个实际用途都在Boost 中:

【讨论】:

  • 但是 +1 表示例外。 :P 你介意详细说明operator, 的模板元编程使用吗?听起来很有趣。
  • 另外,Boost.Parameter 重载了逗号操作符,这是另一种用法。另外,我同意逗号运算符几乎不应该被重载。由于其优先级低,因此难以有效使用。
  • 你也可以在 Eigen 中找到它。
  • @HelloGoodbye :除了我从未说过永远不应该超载它;我说过,作为这个问题的提问者,你永远不应该超载它。 IE。如果您还不知道答案,那么答案永远不会——这绝不是自相矛盾的 IMO。感谢您解释否决票。 :-]
  • 低优先级允许组合几乎所有可以想象的表达式而无需额外的括号——这是该运算符的一个非常简洁的属性。它确实派上用场,多年来我发现它有很多用途,使代码具有可读性和表现力……但我的规则是只在它没有引入任何意外并且使代码的含义显而易见时才使用它没有阅读使用中 API 文档的人。
【解决方案3】:

一种可能性是Boost Assign 库(尽管我很确定有些人会认为这种滥用而不是一种好的用途)。

Boost Spirit 也可能重载了逗号运算符(它几乎重载了其他所有内容......)

【讨论】:

  • 绝对有趣的库! +1
【解决方案4】:

类似于@GMan'sBoost.Assign 示例,Blitz++ 重载逗号运算符以提供convenient syntax 用于处理多维数组。例如:

Array<double,2> y(4,4);   // A 4x4 array of double
y = 1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1;

【讨论】:

    【解决方案5】:

    逗号有一个有趣的属性,它可以接受void 类型的参数。如果是这种情况,则使用内置的逗号运算符。

    当您想确定表达式是否具有 void 类型时,这很方便:

    namespace detail_
    {
        template <typename T>
        struct tag
        {
            static T get();
        };
    
        template <typename T, typename U>
        tag<char(&)[2]> operator,(T, tag<U>);
    
        template <typename T, typename U>
        tag<U> operator,(tag<T>, tag<U>);
    }
    
    #define HAS_VOID_TYPE(expr) \
        (sizeof((::detail_::tag<int>(), \
                 (expr), \
                 ::detail_::tag<char>).get()) == 1)
    

    作为练习,我让读者弄清楚发生了什么。请记住,operator, 关联到右侧。

    【讨论】:

    • “作为练习,我让读者弄清楚发生了什么。”不是答案网站的重点。
    • @infinitezero 这并不难,而且你永远不应该在生产中使用它,尤其是自从那个答案以来已经有五个新标准出现了。
    【解决方案6】:

    同样,我收到了一个带有逗号运算符重载的 github 拉取请求。它看起来像以下

    class Mylogger {
        public:
                template <typename T>
                Mylogger & operator,(const T & val) {
                        std::cout << val;
                        return * this;
                }
     };
    
     #define  Log(level,args...)  \
        do { Mylogger logv; logv,level, ":", ##args; } while (0)
    

    然后在我的代码中我可以做到:

     Log(2, "INFO: setting variable \", 1, "\"\n");
    

    有人可以解释为什么这是一个好的或坏的用例吗?

    【讨论】:

    • 不知道好不好。但避免编写这样的代码:... &lt;&lt; "This is a message on line " &lt;&lt; std::to_string(__LINE__) &lt;&lt; " because variable a = " &lt;&lt; std::to_string(a) &lt;&lt; " which is larger than " &lt;&lt; std::to_string(limit) &lt;&lt; "\n"。这在报告错误或构建异常消息时非常常见。我不确定逗号是否是唯一的选择:任何其他运算符都可以实现这一点,例如operator+operator|operator&amp;&amp; 甚至operator&lt;&lt; 本身。但这是一个插曲。
    • 我认为现代 C++ 会使用可变参数模板。
    • 用问题回答问题不好;-)
    【解决方案7】:

    SOCI - The C++ Database Access Library中用于实现接口的入站部分:

    sql << "select name, salary from persons where id = " << id,
           into(name), into(salary);
    

    来自rationale FAQ

    问:重载的逗号运算符只是混淆,我不喜欢它。

    好吧,考虑以下几点:

    “将查询 X 发送到服务器 Y 并将结果放入变量 Z。”

    上面,“and”起到了逗号的作用。即使重载逗号运算符在 C++ 中不是一种非常流行的做法,一些库也会这样做,从而实现简洁且易于学习的语法。我们很确定,在 SOCI 中,逗号操作符的重载效果很好。

    【讨论】:

      【解决方案8】:

      我使用逗号运算符来索引具有多个索引的地图。

      enum Place {new_york, washington, ...};
      
      pair<Place, Place> operator , (Place p1, Place p2)
      {
          return make_pair(p1, p2);
      }
      
      
      map< pair<Place, Place>, double> distance;
      
      distance[new_york, washington] = 100;
      

      【讨论】:

      • 我真的很喜欢这个,+1。
      • 另一方面,这是为了克服我们只能将一个参数传递给operator[]的事实。有人提出它可以采用多个参数:参见Evolution Defect Report 88
      • 感觉是一种很好的语法,也可以用于多维数组实现,但不幸的是,对于整数类型来说,重载不是那么好。
      • distance[{new_york, washington}] 工作时不会重载任何东西。为了避免如此邪恶的事情,额外的一组括号是一个很小的代价!
      • 如果你调用一个函数 foo(new_york, washington) 会发生什么,它应该有两个不同的地方作为参数?
      【解决方案9】:

      这是来自 OpenCV 文档 (http://docs.opencv.org/modules/core/doc/basic_structures.html#mat) 的示例。逗号操作符用于 cv::Mat 初始化:

      // create a 3x3 double-precision identity matrix
      Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
      

      【讨论】:

        【解决方案10】:

        其中一个实际用途是在宏中有效地使用可变参数。顺便说一句,变量参数是早期 GCC 的一个扩展,现在是 C++11 标准的一部分。

        假设我们有一个class X,它将A 类型的对象添加到其中。即

        class X {
          public: X& operator+= (const A&);
        };
        

        如果我们想将A 的1 个或多个对象添加到X buffer; 中怎么办?
        例如,

        #define ADD(buffer, ...) buffer += __VA_ARGS__
        

        上面的宏,如果用作:

        ADD(buffer, objA1, objA2, objA3);
        

        然后它将扩展为:

        buffer += objA1, objeA2, objA3;
        

        因此,这将是一个使用逗号运算符的完美示例,因为变量参数使用相同的扩展。

        因此,为了解决这个问题,我们重载 comma 运算符并将其包裹在 += 周围,如下所示

          X& X::operator, (const A& a) {  // declared inside `class X`
            *this += a;  // calls `operator+=`
          }
        

        【讨论】:

        • 也许现在应该是template&lt;typename ... A&gt; X&amp; ADD(X&amp; buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}。注意:您可能必须使用(void) sink; 语句来防止优化接收器。这避开了宏,即 imo,甚至更好
        【解决方案11】:

        我使用逗号操作符来打印日志输出。它实际上与ostream::operator&lt;&lt; 非常相似,但我发现逗号运算符实际上更好 用于该任务。

        所以我有:

        template <typename T>
        MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }
        

        它有这些很好的特性

        • 逗号运算符的优先级最低。因此,如果您想流式传输表达式,如果您忘记了括号,事情不会搞砸。比较:

          myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
          myLog, "The result is: ", x&y;
          

          您甚至可以毫无问题地在内部混合比较运算符,例如

          myLog, "a==b: ", a==b;
          
        • 逗号运算符在视觉上很小。把很多东西粘在一起也不会影响阅读

          myLog, "Coords=", g, ':', s, ':', p;
          
        • 它与逗号运算符的含义对齐,即“打印这个”然后“打印那个”。

        【讨论】:

          猜你喜欢
          • 2016-08-22
          • 2011-03-08
          • 2011-01-24
          • 2012-05-04
          • 2020-12-31
          • 1970-01-01
          • 2017-01-23
          • 2010-10-04
          • 2017-07-11
          相关资源
          最近更新 更多