为同一个班级提供不同比较运算符的最佳策略是什么?

时间:2017-12-07 10:32:34

标签: c++ c++11 operators

考虑这个存储价值和时间的简单类。

class A
{
public:
    boost::posix_time::ptime when;
    double value;
};

根据具体情况,我需要按值或按时间比较A的两个实例(和/或将它们存储在set / map中,有时按值排序,有时会按值排序按时间)。

提供operator<会让您感到困惑,因为您无法判断它是按价值还是按时间进行比较。

现在,什么是最好的策略?

  • 是否可以提供operator<参数? (将用作a <(ByTime) b)?
  • 我应该使用lowerThan(比较值)方法和earlierThan(比较时间)方法,将右操作数作为参数吗?但那么,处理<<=>>===!=的最佳做法是什么?每个比较器的一种方法?或者他们可以采取参数(例如bool isLower(bool strict, const A& right) constbool isGreater(bool strict, const A& right) constbool isEarlier(bool strict, const A& right) constbool isLater(bool strict, const A& right) const ......

最佳做法是什么?

4 个答案:

答案 0 :(得分:1)

恕我直言,最通用的方式是两个步骤:

  1. 制作ADL吸气剂。

  2. 根据这些吸气剂编写比较概念。

  3. 示例:

    #include <boost/date_time.hpp>
    #include <set>
    #include <vector>
    #include <algorithm>
    
    class A
    {
    public:
        boost::posix_time::ptime when;
        double value;
    };
    
    // get the 'when' from an A
    auto get_when(A const& a) -> boost::posix_time::ptime 
    { 
        return a.when; 
    }
    
    // get the 'when' from a ptime (you could put this in the boost::posix_time namespace for easy ADL    
    auto get_when(boost::posix_time::ptime t) -> boost::posix_time::ptime 
    { 
        return t; 
    }
    
    // same for the concept of a 'value'
    auto get_value(A const& a) -> double 
    { 
        return a.value; 
    }
    
    auto get_value(double t) -> double 
    { 
        return t; 
    }
    
    // compare any two objects by calling get_when() on them    
    struct increasing_when
    {
        template<class L, class R>
        bool operator()(L&& l, R&& r) const
        {
            return get_when(l) < get_when(r);
        }
    };
    
    // compare any two objects by calling get_value() on them    
    struct increasing_value
    {
        template<class L, class R>
        bool operator()(L&& l, R&& r) const
        {
            return get_value(l) < get_value(r);
        }
    };
    
    
    void example1(std::vector<A>& as)
    {
        // sort by increasing when
        std::sort(begin(as), end(as), increasing_when());
    
        // sort by increasing value
        std::sort(begin(as), end(as), increasing_value());
    }
    
    int main()
    {
        // same for associative collections
        std::set<A, increasing_when> a1;
        std::set<A, increasing_value> a2;
    }
    

    更新

    如果您愿意,可以进行比较:

    template<class Comp>
    struct compare_when
    {
        template<class L, class R>
        bool operator()(L&& l, R&& r) const
        {
    
            return comp(get_when(l), get_when(r));
        }
    
        Comp comp;
    };    
    
    using increasing_when = compare_when<std::less<>>;
    using decreasing_when = compare_when<std::greater<>>;
    

    直接在代码中使用比较:

    auto comp = compare_when<std::greater<>>();
    if (comp(x,y)) { ... }
    

答案 1 :(得分:1)

对UKMonkey的评论作出反应,将我理解的内容定义为“比较类”是一种很好的方法/实践吗?

class A
{
public:
    boost::posix_time::ptime when;
    double value;

    const boost::posix_time::ptime& getTime() const { return when; }
    double getValue() const { return value; }
};

template <typename T>
class CompareBy
{
public:
    CompareBy( const A& a, T (A::*getter)() const ) : a(a), getter(getter)
    {}

    bool operator<( const CompareBy& right ) const
    {
        return (a.*getter)() < (right.a.*getter)();
    }

    // you may also declare >, <=, >=, ==, != operators here

private:
    const A& a;
    T (A::*getter)() const;
};

class CompareByTime : public CompareBy<const boost::posix_time::ptime&>
{
public:
    CompareByTime(const A& a) : CompareBy(a, &A::getTime)
    {
    }
};

class CompareByValue : public CompareBy<double>
{
public:
    CompareByValue( const A& a ) : CompareBy(a, &A::getValue)
    {
    }
};

struct byTime_compare {
    bool operator() (const A& lhs, const A& rhs) const {
        return CompareByTime(lhs) < CompareByTime(rhs);
    }
};

int main()
{
    A a, b;

    ...

    if (CompareByValue(a) < CompareByValue(b))
    {
        ...
    }

    std::set<A, byTime_compare> mySet;
}

答案 2 :(得分:0)

答案简短:不要 我在评论中解释了为什么,主要原因是,它会在代码中引入歧义并降低可读性,这与运算符的意图相反。只需使用不同的方法,并提供选择哪一种用于此类的方法(如比较器)。当我输入这个时,人们发布了很好的例子,甚至有些人使用了一些元编程。

然而,对于科学,你有点可以。虽然你不能向运算符添加一个参数(二元运算符是一个二元运算符,并且似乎没有在某处添加第三个参数的语法),你可以使你的运算符在不同的上下文中表示不同的东西(c ++上下文,代码行或由“{}”分隔的块)

这里使用构造/销毁顺序很快完成(类似的实现到一个简单的锁而不考虑线程安全性):

比较看起来像:

Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), Thing{1, 2} < Thing{3, 4};

在线运行此示例:http://cpp.sh/3ggrq

#include <iostream>

struct Thing {
    struct thingSortingMode {
        enum mode {
            defaultMode,
            alternateMode
        };

        mode myLastMode;

        thingSortingMode(mode aMode) { myLastMode = Thing::ourSortingMode; Thing::ourSortingMode = aMode; std::cout << "\nmode: " << aMode << "\n"; }
        ~thingSortingMode() { Thing::ourSortingMode = myLastMode; std::cout << "\nmode: " << myLastMode << "\n";}
    };

    bool operator < (Thing another) {
        switch (ourSortingMode) //I use an enum, to make the example more accessible, you can use a functor instead if you want
        {
            case thingSortingMode::alternateMode:
                return myValueB < another.myValueB;
                break;

            default:
                return myValueA < another.myValueA;
                break;
        }
    }

    static thingSortingMode::mode ourSortingMode;

    int myValueA;
    int myValueB;
};

Thing::thingSortingMode::mode Thing::ourSortingMode = Thing::thingSortingMode::defaultMode;

int main()
{
  Thing a{1, 1}, b{0, 2}; // b < a in default mode, a < b in alternate mode

  std::cout << (a < b); //false

  {
    Thing::thingSortingMode ctx(Thing::thingSortingMode::alternateMode);


    std::cout << (a < b); //true
    Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), std::cout << (a < b), //false
        Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), std::cout << (a < b); //true

    std::cout << (a < b); //true
  }

  std::cout << (a < b); //false
}

请注意,这个构造/破坏技巧可以管理任何类型的上下文状态,这里有一个更丰富的例子,有4个状态和更多嵌套的上下文

在线运行此示例:http://cpp.sh/2x5rj

#include <iostream>

struct Thing {
    struct thingSortingMode {
        enum mode {
            defaultMode = 1,
            alternateMode,
            mode3,
            mode4,
        };

        mode myLastMode;

        thingSortingMode(mode aMode) { myLastMode = Thing::ourSortingMode; Thing::ourSortingMode = aMode; std::cout << "\nmode: " << myLastMode << " -> " << aMode << "\n"; }
        ~thingSortingMode() { std::cout << "\nmode: " << Thing::ourSortingMode << " -> " << myLastMode << "\n"; Thing::ourSortingMode = myLastMode; }
    };

    static thingSortingMode::mode ourSortingMode;
};

Thing::thingSortingMode::mode Thing::ourSortingMode = Thing::thingSortingMode::defaultMode;

int main()
{
    Thing::thingSortingMode ctx(Thing::thingSortingMode::mode3);
    {
        Thing::thingSortingMode ctx(Thing::thingSortingMode::alternateMode);
        {
            Thing::thingSortingMode ctx(Thing::thingSortingMode::mode4);
            {
                Thing::thingSortingMode ctx(Thing::thingSortingMode::defaultMode);
                std::cout << "end sub 3 (mode 1)\n";
            }

            std::cout << 
                (Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), "this is the kind of things that might behave strangely\n") <<
                (Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), "here both are printed in mode 2, but it's a direct consequence of the order in which this expression is evaluated\n"); //note though that arguments are still constructed in the right state

            std::cout << "end sub 2 (mode 4). Not that we still pop our states in the right order, even if we screwed up the previous line\n";
        }

        std::cout << 
                (Thing::thingSortingMode(Thing::thingSortingMode::alternateMode), "this on the other hand (mode 2)\n"),
        std::cout << 
                (Thing::thingSortingMode(Thing::thingSortingMode::defaultMode), "works (mode 1)\n"); //but pay attention to the comma and in which order things are deleted

        std::cout << "end sub 1 (mode 2)\n";
    }
    std::cout << "end main (mode 3)\n";
}

输出:

mode: 1 -> 3

mode: 3 -> 2

mode: 2 -> 4

mode: 4 -> 1
end sub 3 (mode 1)

mode: 1 -> 4

mode: 4 -> 1

mode: 1 -> 2
this is the kind of things that might behave strangely
here both are printed in mode 2, but it's a direct consequence of the order in which this expression is evaluated

mode: 2 -> 1

mode: 1 -> 4
end sub 2 (mode 4). Not that we still pop our states in the right order, even if we screwed up the previous line

mode: 4 -> 2

mode: 2 -> 2
this on the other hand (mode 2)

mode: 2 -> 1
works (mode 1)

mode: 1 -> 2

mode: 2 -> 2
end sub 1 (mode 2)

mode: 2 -> 3
end main (mode 3)

mode: 3 -> 1

答案 3 :(得分:0)

另一种方法,非常简单:将模板比较器函数添加到A类使得最终可以轻松进行比较,并且非常容易出错:

#include <iostream>
#include <set>

using namespace std;

class A
{
public:
    int when;
    double value;

    int getTime() const { return when; }
    double getValue() const { return value; }

    template<typename T>
    bool isLower( T (A::*getter)() const,
                  bool strict,
                  const A& right ) const
    {
        if ( strict )
            return ((*this).*getter)() < (right.*getter)();
        else
            return ((*this).*getter)() <= (right.*getter)();
    }

    template<typename T>
    bool isGreater( T (A::*getter)() const,
                    bool strict,
                    const A& right ) const
    {
        if ( strict )
            return ((*this).*getter)() > (right.*getter)();
        else
            return ((*this).*getter)() >= (right.*getter)();
    }

    template<typename T>
    bool isEqual( T (A::*getter)() const,
                  const A& right ) const
    {
        return ((*this).*getter)() == (right.*getter)();                  
    }
};

struct byTime_compare {
    bool operator() (const A& lhs, const A& rhs) const {
        return lhs.isLower( &A::getTime, true, rhs );
    }
};

int main()
{
    A a, b;

    if ( a.isLower( &A::getValue, true, b ) ) // means a < b by value
    {
        // ...
    }

    std::set<A, byTime_compare> mySet;
}