【问题标题】:std::set select less or greater comparator at runtimestd::set 在运行时选择更少或更大的比较器
【发布时间】:2012-06-25 07:38:50
【问题描述】:

我正在重构一些代码,发现有两个地方可以用相同的代码编写,只是集合的比较器在一个地方是less<double>,在另一个地方是greater<double>。比如:

double MyClass::Function1(double val)
{
    std::set<double, less<double> > s;
    // Do something with s
}

double MyClass::Function2(double val)
{
    std::set<double, greater<double> > s;
    // Do the same thing with s as in Function1
}

所以我想这样做:

double MyClass::GeneralFunction(double val, bool condition)
{  
    if(condition)  
    {  
        // Select greater as comparator  
    }  
    else
    {  
        // Select less as comparator  
    }  

    set<double, comparator> s;  
    // common code
}

我已经通过使用我的自定义比较器函数使其工作,如下所示:

bool my_greater(double lhs, double rhs)
{
    return lhs > rhs;
}

bool my_less(double lhs, double rhs)
{
    return lhs < rhs;
}

double MyClass::GeneralFunction(double val, bool condition)
{ 
    typedef bool(*Comparator) ( double,  double);
    Comparator comp = &my_less;
    if (condition)
    {
        comp = &my_greater;
    }

    std::set<double, Comparator > s(comp);  

    //....
}

但我想使用内置的。问题是我不知道如何声明比较器并为其分配内置谓词。

任何帮助将不胜感激。

【问题讨论】:

    标签: c++ stl set predicate


    【解决方案1】:

    您真的需要运行时检查吗?

    template <class Comp> double MyClass::Function(double val)
    {
        std::set<double, Comp > s;
        // Do something with s
    }
    

    即使你这样做了,你仍然可以使用

    double MyClass::Function(double val, bool comp)
    {
       return comp ? Function<std::less<double> >(val) : Function<std::greater<double> >(val);
    }
    

    【讨论】:

    • 谢谢!我真的不需要运行时检查,所以我可以在调用者中选择比较器。
    【解决方案2】:

    问题是你不能选择比较器的类型 tuntime 和 std::lessstd::greater 具有不相关的类型。 同样,std::set 实例化为 std::less 作为比较器 具有与std::greater 实例化的 on 无关的类型。有 几种可能的解决方案,但最简单的(也是唯一一个不是 涉及继承,虚函数和动态分配)是沿 你在做什么:

    class SelectableCompare
    {
        bool myIsGreater;
    public:
        SelectableCompare( bool isGreater ) : myIsGreater( isGreater ) {}
        bool operator()( double d1, double d2 ) const
        {
            static std::less<double> const less;
            return myIsGreater
                ? less( d2, d1 )
                : less( d1, d2 );
        }
    };
    

    我使用了标准的std::lessstd::greater,因为你 表示有兴趣这样做。在double 的情况下,这是, 坦率地说,矫枉过正;我通常只写d1 &gt; d2d1 &lt; d2。一种 然而,上述的模板版本可能是有意义的,因为有些 类型可能有一个专门的std::less。这也是为什么我只使用 std::less;可以想象程序员只专攻 std::less,知道这是唯一用于 在标准库中排序。

    只是为了完整:显而易见的替代方法是使用该策略 比较器中的模式,具有抽象的比较器基础:

    class Comparator
    {
    public:
        virtual ~Comparator() {}
        virtual bool isLessThan( double d1, double d2 ) const = 0;
    };
    

    ,用于不同比较的相当明显的派生类,以及 管理内存的包装器:

    class ComparatorWrapper
    {
        std::shared_ptr<Comparator> myComparator;
    public:
        ComparatorWrapper( Comparator* newed_comparator )
            : myComparator( newed_comparator )
        {
        }
        bool operator()( double d1, double d2 ) const
        {
            return myComparator->isLessThan( d1, d2 );
        }
    };
    

    这对于您需要的二元选择来说绝对是矫枉过正,但可能 如果有更多的选择是合适的;例如set 可能是 按许多不同字段之一(所有不同类型)排序。

    【讨论】:

      【解决方案3】:

      随便用

      std::set<double, std::function<bool(double,double)>>
      

      作为你的集合,并像这样实例化它:

      typedef std::set<double, std::function<bool(double,double)> > RTSet;
      
      RTSet choose_ordering(bool increasing)
      {
          if (increasing)
              return RTSet( std::less<double>() );
          else
              return RTSet( std::greater<double>() );
      }
      

      请注意,一般而言,您的权衡是:

      • 检查每次比较的顺序,或者
      • 在实例化时检查一次,然后在每个函数调用(例如虚函数调用)上产生一个间接调用

      我更喜欢第二个选项,这样您就不会在使用集合时意外更改顺序,从而破坏其所有不变量。


      只是一个快速的想法,因为这可能是一个单独的答案(甚至是问题),但是你提到除了排序顺序之外,两段代码是相同的。

      我在某些情况下使用的另一种方法是使用单一排序方向,并在集合上将代码操作模板化(按迭代器类型),因此您可以这样做

      if (increasing)
          do_stuff(set.begin(), set.end());
      else
          do_stuff(set.rbegin(), set.rend());
      

      【讨论】:

      • +1 用于std::function。关于在使用集合时意外更改顺序的风险:任何标志都应该是功能对象的私有成员,这大大降低了风险。
      • std::function 部分是我真正要问的,但我认为 MSalters 的回答是一个更好的解决方案。
      【解决方案4】:

      为什么不这样做

      template <typename Compare>
      double MyClass::GeneralFunction(double val)
      {
          std::set<double, Compare> s;
      
          //....
      }
      

      通过形参选择模板并不是 C++ 处理得很好。通过让调用者提供模板参数,尽可能多地进入编译阶段。

      然后你可以提供一个包装器,如果你真的想在运行时选择一个:

      double MyClass::GeneralFunction(double val, bool condition)
      {
          return condition ?
              GeneralFunction<std::greater<double> >(val) :
              GeneralFunction<std::less   <double> >(val);\
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-06
        • 1970-01-01
        • 2017-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多