5

我正在重构一些代码,发现有两个地方可以用相同的代码编写,除了一个集合的比较器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);  

    //....
}

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

任何帮助将不胜感激。

4

4 回答 4

4

问题是您无法在 tuntime 选择比较器的类型std::less并且std::greater具有不相关的类型。类似地,作为比较器std::set实例化的 withstd::less具有与实例化 with 无关的类型std::greater。有几种可能的解决方案,但最简单的(也是唯一一个不涉及继承、虚函数和动态分配的解决方案)与您正在做的事情一致:

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 > d2and d1 < 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,可能会在许多不同字段之一(所有不同类型)上排序。

于 2012-06-25T08:38:29.333 回答
4

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

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);
}
于 2012-06-25T07:50:39.913 回答
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());
于 2012-06-25T08:32:59.047 回答
2

为什么不做

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);\
}
于 2012-06-25T07:49:27.030 回答