【问题标题】:How do you structure your comparison functions?你如何构建你的比较函数?
【发布时间】:2009-02-28 05:10:20
【问题描述】:

我经常遇到这样的情况,尤其是在 C++ 中进行排序时,我正在比较一系列字段以比较更大的结构。一个简化的例子:

struct Car{
    Manufacturer make;
    ModelName model;
    Year year;
};

bool carLessThanComparator( const Car & car1, const Car & car2 ){
    if( car1.make < car2.make ){
        return true;
    }else if( car1.make == car2.make ){
        if( car1.model < car2.model ){
            return true;
        }else if( car1.model == car2.model ){
            if( car1.year < car2.year ){
                return true;
            }
        }
    }

    return false;
}

我的直觉方法似乎很麻烦,尤其是对于 3 个以上的领域。您将如何在 C++ 中构建这一系列比较?其他语言是否提供更简洁或更优雅的语法?

【问题讨论】:

  • 个人风格 - 我会实现一个类似strcmp() 的函数(或方法),如果 a 等于 b,则返回 0,如果 a 大于 b,则返回 1,如果 a 小于 b,则返回 -1 ,然后根据比较函数定义比较运算符(如果您愿意)和/或方法。那么我们就只有bool carLessThanComparator( const Car &amp; car1, const Car &amp; car2 ) { return cmp(car1, car2) == -1; }

标签: c++ coding-style language-features lexicographic stdtuple


【解决方案1】:

好吧,如果您的函数在 if 子句中返回,则不需要显式 else,因为它已经退出了。这可以节省“缩进谷”:

bool carLessThanComparator( const Car & car1, const Car & car2 ) {
    if( car1.make < car2.make )
        return true;

    if ( car1.make != car2.make )
        return false;

    if( car1.model < car2.model )
        return true;

    if( car1.model != car2.model )
        return false;

    if( car1.year < car2.year )
        return true;

    return false;
}

我也喜欢 MarkusQ 的 LISPish 短路方法。

【讨论】:

  • 为什么不把“return car1.year
  • 只是为了对称——我希望代码示例尽可能清晰。在实践中,我会按照你说的去做,或者真的像 Markus 那样编写代码。
【解决方案2】:

如果这种情况经常发生,您可以将这样的模板放入通用标题中:

template<typename T, typename A1, typename A2, typename A3>
bool
do_less_than(
        const typename T& t1,
        const typename T& t2,
        const typename A1 typename T::* a1,
        const typename A2 typename T::* a2,
        const typename A3 typename T::* a3)
{
    if ((t1.*a1) < (t2.*a1)) return true;
    if ((t1.*a1) != (t2.*a1)) return false;
    if ((t1.*a2) < (t2.*a2)) return true;
    if ((t1.*a2) != (t2.*a2)) return false;
    return (t1.*a3) < (t2.*a3);
}

根据需要为不同数量的参数添加其他模板。对于每个小于函数,您可以执行以下操作:

bool carLessThanComparator(const Car& car1, const Car& car2)
{
    return do_less_than(car1, car2, &Car::make, &Car::model, &Car::year);
}

【讨论】:

  • 我很少在 C++ 中看到指向成员的语法。很有趣。
  • 这就是为什么
  • 主要的一点是复杂在一个库中,一次创建,而普通代码变得更简单。实际比较函数中的比较顺序很明显,没有倒排比较的bug。
【解决方案3】:

我个人建议不要像我们在这里推荐的那样使用 != 或 == 运算符 - 这要求参数/成员同时具有 less then 和 equal 运算符,只是为了检查包含它们的类- 仅使用 less then 运算符就足够了,并且会为您节省冗余和将来的潜在缺陷。

我建议你写:

bool operator<(const Car &car1, const Car &car2) 
{
    if(car1.make < car2.make)
        return true;
    if(car2.make < car1.make)
        return false;

    if(car1.model < car2.model)
        return true;
    if(car2.model < car1.model)
        return false;

    return car1.year < car2.year;
}

【讨论】:

  • 好点。这确实允许您在较大的结构上实现小于比较,只要求每个成员都可以使用小于比较。
【解决方案4】:

我知道这是一个老问题,但对于未来的访问者:现代 C++11 解决方案是使用 std::tie

struct Car{
    Manufacturer make;
    ModelName model;
    Year year;
};

bool operator<(Car const& lhs, Car const& rhs)
{
    return std::tie(lhs.make, lhs.model, lhs.year) < std::tie(rhs.make, rhs.model, rhs.year);
}

std::tie 将结构转换为std::tuple,以便上述比较运算符委托给std::tuple::operator&lt;。这反过来对成员编组为std::tie 的顺序进行了字典比较。

词典比较的短路方式与此问题的其他解决方案相同。但是,在 C++ lambda 表达式中动态定义就足够简洁了。对于具有私有数据成员的类,最好在类内部定义为friend 函数。

【讨论】:

    【解决方案5】:
    bool carLessThanComparator( const Car & car1, const Car & car2 ){
        return (
          ( car1.make  < car2.make  ) or (( car1.make  == car2.make  ) and
          ( car1.model < car2.model ) or (( car1.model == car2.model ) and
          ( car1.year  < car2.year  ) 
          )));
    

    -- MarkusQ

    【讨论】:

      【解决方案6】:

      就我个人而言,我会覆盖 ==、 和任何其他需要的运算符。这将清理代码,而不是在比较中,而是在您需要进行比较时。 对于实际的比较本身,我会按照 Crashworks 说的那样写。

      bool operator<(const Car &car1, const Car &car2) {
          if(car1.make < car2.make)
              return true;
          if(car1.make != car2.make)
              return false;
          if(car1.model < car2.model)
              return true;
          if(car1.model != car2.model)
              return false;
          return car1.year < car2.year;
      }
      

      【讨论】:

      • 为什么不把“return car1.year
      • 我从来没有真正看过 boost 太多,但只是查找 boost::totally_ordered,它会很有用。至于最后一条语句......当我看到它与我使用的样式相同时,我实际上只是复制了 Crashwork 的代码。像你说的那样使用 return 会更好。
      【解决方案7】:

      我想知道和 OP 一样的事情,偶然发现了这个问题。阅读答案后,我受到 janm 和 RnR 的启发,编写了一个 lexicographicalMemberCompare 模板函数,该函数仅在比较成员上使用 operator&lt;。它还使用boost::tuple,以便您可以指定任意数量的成员。这里是:

      #include <iostream>
      #include <string>
      #include <boost/tuple/tuple.hpp>
      
      template <class T, class Cons>
      struct LessThan
      {
          static bool compare(const T& lhs, const T& rhs, const Cons& cons)
          {
              typedef LessThan<T, typename Cons::tail_type> NextLessThan;
              typename Cons::head_type memberPtr = cons.get_head();
              return lhs.*memberPtr < rhs.*memberPtr ?
                  true :
                  (rhs.*memberPtr < lhs.*memberPtr  ?
                      false :
                      NextLessThan::compare(lhs, rhs, cons.get_tail()));
          }
      };
      
      template <class T>
      struct LessThan<T, class boost::tuples::null_type>
      {
          static bool compare(const T& lhs, const T& rhs,
                              const boost::tuples::null_type& cons)
          {
              return false;
          }
      };
      
      template <class T, class Tuple>
      bool lexicographicalMemberCompare(const T& lhs, const T& rhs,
                                        const Tuple& tuple)
      {
          return LessThan<T, typename Tuple::inherited>::compare(lhs, rhs, tuple);
      }
      
      struct Car
      {
          std::string make;
          std::string model;
          int year;
      };
      
      bool carLessThanCompare(const Car& lhs, const Car& rhs)
      {
          return lexicographicalMemberCompare(lhs, rhs,
              boost::tuples::make_tuple(&Car::make, &Car::model, &Car::year));
      }
      
      int main()
      {
          Car car1 = {"Ford", "F150", 2009};
          Car car2 = {"Ford", "Escort", 2009};
          std::cout << carLessThanCompare(car1, car2) << std::endl;
          std::cout << carLessThanCompare(car2, car1) << std::endl;
          return 0;
      }
      

      希望这对某人有用。

      【讨论】:

      • 你有一个operator&gt;。此外,比较函数往往是在类之外编写的。否则非常漂亮。
      • 糟糕。修复了operator&gt; 并将operator&lt; 放在课堂外。我没有收到关于为什么将比较运算符放在类中不好的备忘录(当 lhs 已经是类本身时)。
      • 即使只是operator&lt; 之类的,为了保持一致性,最好还是把它放在课堂之外。
      • 哎呀,我在抽什么?现在可以在 C++11 中使用 std::tie 在一行中完成此操作。请参阅 TemplateRex 的回答:stackoverflow.com/a/15723660/245265
      猜你喜欢
      • 2013-10-07
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 2011-12-28
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 2021-07-20
      相关资源
      最近更新 更多