【问题标题】:What's the best strategy to provide different comparison operators for the same class?为同一类提供不同比较运算符的最佳策略是什么?
【发布时间】:2017-12-08 00:57:04
【问题描述】:

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

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...

最佳做法是什么?

【问题讨论】:

  • 有什么理由不能提供比较器类的排序?
  • ^ 这个,当有不同的合法方式对事物进行排序时,这意味着如果有人阅读“”或“==”,不同的人可能会有不同的解释(理解:错误大多数时候),因此使代码变得更糟且更难维护。我可以看到实现它的方法(比如在上下文中设置运算符的含义),但是比较器是在 c++ 中为排序目的提供不同比较的标准方法
  • @UKMonkey:什么是“比较器类”,是不是一种设计模式,能详细说明一下吗?如果概念是有一个CompareByTime 类和一个CompareByDate 类,这可能是一个可接受的解决方案。
  • range-v3 在其算法中具有 投影 的概念:ranges::sort(As, std::greater<>{}, &A::when);
  • 我认为只有将整个对象与相同或相似类型的对象进行比较时,比较运算符才有意义。如果您只是希望您的对象参与不同的排序,您可以提供对对象排序的属性的访问,并使用它们的比较运算符和常用算法和比较函数。所以你不用比较而是排序。

标签: c++ c++11 operators


【解决方案1】:

恕我直言,最通用的方法是两步过程:

  1. 制作 ADL 吸气剂。

  2. 根据这些 getter 编写比较概念。

示例:

#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)) { ... }

【讨论】:

  • 这对于容器来说看起来不错,但是如果声明一个结构,你将如何处理像 if ( a &lt; b ) 按时间或值,if ( a &lt;= b ) 按时间或值,if ( a &gt; b ) 按时间或值这样的情况每一个?
  • @jpo38 我们可以根据标准比较运算符列表 std::less 等将比较操作设为模板
  • 适当地注意到编写尾随返回类型的悲伤;)
【解决方案2】:

对 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;
}

【讨论】:

  • byTime_compare 正是我的想法。 CompareByTime 类我觉得有点过分了,但我能看出你在想什么
【解决方案3】:

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

但是,对于科学来说,你可以。虽然您不能向运算符添加参数(二元运算符是二元运算符,并且似乎没有在某处添加第三个参数的语法),但您可以使运算符在不同的上下文中具有不同的含义(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

【讨论】:

  • 我不喜欢这种静态方法。它不是线程安全的,并且可能导致嵌套语句中出现意外行为...
  • @jpo38 在线程方面当然可以做得更好,它试图展示的主要内容是如何使用对象生命周期规则来翻转开关。因此,它实际上在嵌套语句中工作得很好(它有点像一堆上下文)。既然你指出了这一点,我可能应该详细说明它为什么会起作用,因为它并不明显
  • 哦,让我重新表述一下“switch”,我所说的 switch 是指状态,任何类型的上下文状态(比如我想在这个上下文中使用的 wich 运算符)
  • 但这并不能阻止开发人员滥用operator&lt;...他可能会忘记在使用它之前正确设置上下文。
  • 这正是我在第一段中所说的。您要求一种使操作员根据其使用的上下文执行不同操作的方法。有可能作弊并获得类似的语法(Thing::thingSortingMode(Thing::thingSortingMode::myMode), Thing{1, 2} &lt; Thing{3, 4};),但这证明了我的观点,这不是处理问题的好方法因为您不知道您正在使用哪个版本的运算符,并且它会在此处显示更多的显式上下文。现在,如果您可以为运算符提供第三个参数,这将是一个好习惯吗?我仍然认为这会令人困惑
【解决方案4】:

另一种方法,非常简单:在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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-08
    • 1970-01-01
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2011-02-10
    • 2012-10-19
    相关资源
    最近更新 更多