【问题标题】:Is this use of the "," operator considered bad form?这种“,”运算符的使用是否被认为是错误的形式?
【发布时间】:2011-10-07 09:55:13
【问题描述】:

我创建了一个列表类,作为替换程序中可变参数函数的一种方法,用于初始化需要包含不断变化的元素列表的对象。 list 类有一个我非常喜欢的用法语法。但是我以前没有见过它使用过,所以我想知道我是否应该仅仅因为这个事实而使用它?列表类的基本实现如下所示...

#include <list>
#include <iostream>
template<typename T>
struct list
{
    std::list<T> items;
    list(const list&ref):items(ref.items){}
    list(){}
    list(T var){items.push_back(var);}
    list& operator,(list add_){
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
    list& operator=(list add_){
        items.clear();
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
    list& operator+=(list add_){
        items.insert(items.end(),add_.items.begin(), add_.items.end());
        return *this;
    }
};

这让我可以像这样在代码中使用它...

struct music{
//...
};
struct music_playlist{
    list<music> queue;
//...
};
int main (int argc, const char * argv[])
{
    music_playlist playlist;
    music song1;
    music song2;
    music song3;
    music song4;
    playlist.queue = song1,song2; // The queue now contains song1 and song2
    playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
    playlist.queue = song2; //the queue now only contains song2
    return 0;
}

我真的认为语法比我刚刚公开一个常规的 stl 容器要好得多,甚至比可变参数函数更好(和类型安全)。但是,由于我没有看到使用这种语法,我很好奇是否应该避免它,因为首先代码应该很容易被其他程序员理解?

编辑:

结合这个问题,我发布了这个question,更针对实际问题的解决方案。

【问题讨论】:

  • ,在高尔夫中被大量使用,这表明它通常是一种不好的做法。在这种情况下,它在大多数情况下看起来更具可读性。也许避免使用 , with = ?每个人都应该清楚 += 发生了什么,即使他们不知道 , 做了什么。使用 = 就不太直观了。
  • 如果您使用+=,为什么不使用+? (老实说,我一直讨厌+ 的这种用法,因为它不是可交换的……但这就是std::string 所做的,而且您已经通过+= 这样做了,所以为什么不保持一致呢?)
  • 你的赋值运算符比你的逗号运算符差很多
  • 这只是我在 5 分钟内想出的一个简单示例。如果我要在实际代码中使用它,我会添加更多功能、错误检查,并将其包含在我的库的命名空间中,以及其他一些东西。
  • 我只是拿一个代码 sn-p 给你团队中最年轻的两个人看,让他们在操作员附近修复一些重要的东西,除了代码之外没有任何解释。如果他们必须来找你问一个问题或花费过多的时间,这意味着你正在浪费团队的时间在这个结构上——如果不是,那应该没问题。

标签: c++ operator-overloading comma


【解决方案1】:

在很多层面上都很糟糕......

您正在覆盖list 和shadowingstd::list。一个很大的禁忌。如果您想要自己的列表类 - 使用不同的名称,不要隐藏标准库。

以这种方式使用, 是不可读的。运算符的返回值是右操作数。即使您的代码有效,对于外部读者来说,原因也不是很明显,这是一件坏事。代码应该是可读的,不是很好。

【讨论】:

  • 我知道原来的问题是一个非常基本的例子。然而,在实际应用中,音频解码和音频渲染发生在不同的线程上,这是不必要的。因此,最终的列表修改需要为完全定义的行为进行互斥保护。最初的实现依赖于程序员自己锁定和解锁互斥锁。但是很多时候它被遗忘了,导致难以调试错误。然后它是使用各种风格的可变参数函数实现的,这也导致了难以调试的问题。那么你将如何实现所需的功能呢?
  • @RTS - 滥用operator , 如何解决纪律程序员或执行代码审查的问题?
  • 因为那些不守纪律的程序员是我的客户。我知道这完全是对运营商的滥用。所以这就是我寻求替代方案和意见的原因。
  • @RTS - 这对于一个完全不同的问题来说已经足够了。建议你发布它,我保证写一个答案。我认为将其隐藏在 cmets 中会丢失很多东西。
  • –1 来自我。覆盖标准名称是完全可以的,这就是命名空间的用途。使用逗号操作符是有问题的,但是 Boost.Assign 将它用于这个确切的目的,因此具有强大的优先级,而且它实际上非常可读。
【解决方案2】:

使用逗号operator , 并没有什么不好的地方。如果被利用,任何操作员都会留下不好的味道。在您的代码中,我没有看到任何合理的问题。我只想提出一个建议:

list& operator,(list &add_){  // <--- pass by reference to avoid copies
  *this += add_;  // <--- reuse operator +=
  return *this;
}

这样,如果您想对逻辑进行任何更改,您必须始终只编辑 operator +=。请注意,我的回答是一般而言的可读性和代码维护的角度。我不会对您使用的业务逻辑提出担忧。

【讨论】:

  • 那里的非常量引用将使编译器使用内置的operator, :) 整个事情通过创建临时列表来工作:list, temporary_list, temporary_list
  • @iammilind:逗号运算符实际上存在其他人没有遇到的问题。真正的缺陷,恕我直言,(s1, s2) 所做的事情与f(s1,s2) 完全不同......在operator() 面前,它很快就会变得混乱。
【解决方案3】:

为什么不像 QList 那样重载 &lt;&lt; 运算符呢?然后像这样使用它:

playlist.queue << song1 << song2; // The queue now contains song1 and song2
playlist.queue << song1 << song3 << song4; //The queue now contains two song1s and song2-4

【讨论】:

  • +1:即使这看起来更难看(我认为确实如此),但它至少具有移位运算符重载的其他用途的先例,以表示“将对象放在某处”从 iostreams 开始。所以我认为它是惯用的,并且比重载逗号运算符更不容易让代码新手感到困惑。
  • 我最初是使用它,但是,您将如何处理列表替换,
  • @RTS:我不会。只需分配给一个空列表,或调用clear 函数或类似的东西,然后继续插入。
  • @RTS:窃取另一个 iostreams 想法,操纵者:playlist.queue &lt;&lt; music_playlist::clear &lt;&lt; song2;
  • @RTS:是C++,不能随意创建运算符。我建议只使用=reset 函数?
【解决方案4】:

这可能属于程序员的事情,但这是我的两分钱。

如果您谈论的代码上下文相当狭窄,用户会在几个地方使用它,仅此而已,那么重载, 运算符可能就可以了。如果您正在构建一种特定领域的语言,该语言用于特定领域而不是其他任何地方,那可能没问题。

当您为希望用户以某种频率使用的东西重载它时,问题就出现了。

重载, 意味着读者需要完全重新解释他们是如何阅读您的代码的。他们不能只看一个表情就能立即知道它的作用。您搞乱了 C++ 程序员在扫描代码时所做的一些最基本的假设。

这样做后果自负。

【讨论】:

    【解决方案5】:

    最近优先级有变化吗?

    playlist.queue = song1,song2;
    

    这应该解析为:

    (playlist.queue = song1) , song2;
    

    你的 ',' 和 '+=' 是一样的! 如果您的逗号运算符要创建一个临时列表,插入左右项目并返回临时列表,那将是更好的语义匹配。那你就可以这样写了;

    playlist.queue = (song1,song2);
    

    带有明确的括号。这将使 C 程序员有机会阅读代码。

    【讨论】:

    • 关于它的解析方式以及它与operator += 相同的事实是正确的。它运行正常,所以我不确定他是否打算复制这两者,或者他是否有其他想法。无论哪种方式,您的建议虽然在理论上很好,但对存储在容器中的对象类型进行了限制......它们必须重载operator ,(T, T),并且它必须返回适当的容器类型。这几乎不会发生在像这样的通用容器中,并且永远不会发生在内置类型中。
    • OP 是故意这样做的。再次,我将您推荐给推广此技巧的 Boost.Assign。重载operator ,(T, T)不是的方法。 C 程序员的论点是无关紧要的。 C 和 C++ 是根本不同的语言,用户尝试转移知识需要自己承担风险。如果你对一种语言做出错误的假设,那是你的错。
    • @Konrad:在最后一行,您很可能可以将C-programmers 替换为普通的其他程序员。我知道 boost.assign 使用这个逗号运算符的重载,但它的做法不同,它将它应用于非常特定于小段代码的类型,其中损害是有限的。也就是说,它们不会重载operator,(std::list&lt;T&gt;,T),而是将operator, 应用于对象旨在一次性使用的类型(某种代理)。 operator, 在长寿命对象中增加了难以发现错误的机会,并且难以阅读。
    • @Dennis,虽然我同意你的说法,但在我看来这不应该是一个“通用容器”。我的猜测是,'list' 只是一个等待一个很酷的名字的 songQueue 类型的占位符。我想您需要实现所有各种排列: Q operator ,(Song, Song); Q 算子 ,(Song, Q); Q算子,(Q, Song); Q算子,(Q, Q);
    【解决方案6】:

    我同意您编写的语法看起来不错。
    我对代码的主要困难是我希望以下内容是相同的

    playlist.queue = song1,song2;
    playlist.queue = (song1,song2);  //more of c-style, as @Iuser notes.
    

    而事实上它们完全不同。

    这很危险,因为它很容易在代码中引入使用错误。 如果有人喜欢使用括号来增加对分组的额外强调(并不少见),那么逗号可能会成为一种真正的痛苦。例如,

    //lets combine differnt playlists
    new_playlist.queue =    song1        //the first playlist
                          ,(song3,song4) //the second playlist //opps, I didn't add song 3!
                          , song5;        //the third 
    

    new_playlist.queue = (old_playlist.queue, song6); //opps, I edited my old playlist too!
    

    顺便说一句,你有没有遇到 boost.assign:http://www.boost.org/doc/libs/1_47_0/libs/assign/doc/index.html

    【讨论】:

      【解决方案7】:

      一个问题是,如果编译器无法选择重载的运算符逗号,它可以使用内置运算符。

      相比之下,Boost.Assign 混合类型会产生编译错误。

      #include <boost/assign.hpp>
      
      int main()
      {
          int one = 1;
          const char* two = "2";
          list<int> li;
          li = one, two;
      
          using namespace boost::assign;
          std::list<int> li2;
          li2 += one, two;
      }
      

      【讨论】:

        【解决方案8】:

        我很好奇我是否应该避免它,因为最重要的是 代码应该很容易被其他程序员理解

        如果目标是让其他 C++ 程序员更容易理解您的代码,那么重写运算符以赋予它们与标准 C++ 非常不同的含义并不是一个好的开始。读者不必 a) 了解您是如何实现容器的;b) 重新调整他们对标准运算符的理解,以便能够理解您的代码。

        对于这种事情,我很感激Boost precedent。如果您非常确定大多数阅读您的代码的人也会熟悉 Boost Assign,您自己的运算符覆盖可能是相当合理的。尽管如此,我还是建议按照@badzeppelin 的建议使用 operator

        cout << "Hello world!"`
        

        您的追加操作与写入流非常相似。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-06
          • 2016-09-13
          • 1970-01-01
          • 1970-01-01
          • 2010-11-26
          • 1970-01-01
          • 2021-11-26
          • 2023-02-05
          相关资源
          最近更新 更多