C++ comparison operators

2019-08-10 15:46发布

问题:

I frequently see people only overriding operator<, not > or ==. Does it mean that by default, operator> and operator== are implemented using operator< ?

I also frequently see people writing (see here)

bool operator() (Node const& n1, Node const& n2) const
{
    // TODO: your condition
    return n1.a < n2.a;
}

What does operator() mean here then? it seems very counter-intuitive.

回答1:

The reason they're only overriding < is because by default that's what ordered containers use to compare values, so that's all they need to define to answer the question.

#include <set>

struct my_fancy_integer
{
    int fancy;
};

// This is all std::set (or any ordered container) needs by default,
// So for an example answer I won't do anything else (as I expect you to
// learn and understand *why* it needs this by default).
bool operator<(const my_fancy_integer& first, const my_fancy_integer& second)
{
    return first.fancy < second.fancy;
}

// But I should really also defined the other comparison operators...
// For example, without operator> defined this would fail:
//
// std::set<my_fancy_integer, std::greater<my_fancy_integer>> x;
//
// But since you read documentation for std::set and std::greater you
// understand why this fails: std::set will use std::greater to order
// the values, and std::greater (by default) will try to use operator>.

int main()
{
    std::set<my_fancy_integer> x; // okay
}

No, the other operators are not implicitly defined in terms of it (nor in terms of anything else). In a real application, if you've defined one you should define them all.

Alternatively, if < doesn't make sense for your type syntactically, but ordering them is still valuable, define a usable default predicate that users should pass to the ordered container's predicate template argument.

#include <set>
#include <string>
#include <tuple>

struct my_employee
{
    std::string name;
    int salary;
    int yearsEmployed;
};

// Saying one employee is "less" than another doesn't really make sense...
// But I can still give an *ordering* on them:
struct my_employee_ordering
{
    bool operator()(const my_employee& first, const my_employee& second) const
    {
        // I'll just reuse std::tuple's comparison operator, and tie the
        // fields of each structure into a tuple to use it. This orders
        // by name, salary, then yearsEmployed.
        return std::tie(first.name, first.salary, first.yearsEmployed) <
               std::tie(second.name, second.salary, second.yearsEmployed);
    }
};

int main()
{
    // We need to tell std::set how to order employees:
    std::set<my_employee, my_employee_ordering> x; // okay
}

operator() is the function call operator. It allows your object to be "called":

struct foo
{
    void operator()(int x) { std::cout << x << std::endl; }
};

foo f;
f(5); // calls foo::operator()(5)


回答2:

First off, no. An implementation of < does not implicitly define == and >. People tend to define < because the standard library uses the less than operator specifically comparisons for list sorting and similar tasks.

operator() is called the function call operator. Basically, let's say I have a struct foo as follows

struct foo {
    int operator()(int a, int b) {
        return a+b;
    }
};

Now, if I have an instance of foo called x, I can use x(6, 5) and it will call the function call operator with the two parameters I gave (6 and 5 in this case). The function call operator is just for treating structures like a function and can take any number and type of parameters or even take no parameters. In the example you gave, when the object encompassing that function is used as a function call, it will compare the two node objects and return true if the first is less than the second according to the < operator.



回答3:

The minimal comparison or ordering operators defined are < and ==. The other comparison operators can be defined in terms of these:

operator != -- !operator==
operator >= -- !operator<
operator <= -- operator== || operator <
operator >  -- !(operator== || operator <)

The boost library contains templates that will generate all the other operators. See "less_than_comparable" for an example.

Edit 1:
The operator() is defining an ordering operation, which is often times used by sorting functions. For example, you could have one function defined for ascending ordering and another function defined for descending ordering. To sort, you would pass either the ascending function object or the descending function object.



回答4:

What you're seeing are people implementing special-purposed functors and not general purpose objects; this is a case where C++ "lets you do" but doesn't "let you do explicitly".

Thus you are seeing overloading of "operator<" for the case where a function is used for weak ordering in an ordered container where comparison is not required. The class is only used for this purpose and therefore comparison operators etc don't need to be implemented.

operator() is used for predicate functors to allow the object to be "called" in a clear way:

struct EqualityPredicate {
    bool operator()(const Node& lhs, const Node& rhs) const { return lhs == rhs; }
};

EqualityPredicate& equality;
for (Node hay : haystack) {
    if(equality(hay, haystack))
        doWork(hay);
}

Which is calling equality.operator()(hay, haystack);



回答5:

It is a common task where you have to overload/override or define custom comparison operators for your objects, for example to store them in sets/unordered_sets or to use the objects as keys in maps/unordered_maps. For that you have to define your "less than" operator(<), equal operator(==) and "hash" operator. C++ allows to do it in different ways. My preferred way is to define them inside of your object. So, you have a better overview of the object behavior. The example I have created may make not much sense in the real world, but demonstrates the idea of customized behavior.

#include<assert.h>
#include<set>
#include<string>
#include<unordered_map>
#include<unordered_set>
#include<map>

using namespace std;

struct Person
{
    string name;
    unsigned age;
    double wage;
    Person() :name(""), age(0), wage(0.0) {}
    Person(const string& n, unsigned a, double w) :name(n), age(a), wage(w) {}

    Person & operator=(const Person& p) {
        if (this == &p)
            return *this;
        this->name = p.name;
        this->age = p.age;
        this->wage = p.wage;
        return *this;
    }
    // less than oprator for sets
    bool operator<(const Person& other) const {
        return this->wage < other.wage;
    }
    // equal oprator for maps
    bool operator==(const Person& other)const {
        return ((this->name == other.name) && (this->age == other.age));
    }
    //hash operator for unordered_sets/unordered_maps
    size_t operator()(const Person& p) const {
        return std::hash<string>()(p.name);
    }
};

int main()
{
    set<Person> personsSet;
    Person a("a", 20, 3000.0), b("b", 30, 2000.0), c("c", 40, 1000.0), d("d", 25, 500.0), e("e", 31, 700.0);
    personsSet.insert(a);
    assert(personsSet.size() == 1);
    personsSet.insert(b);
    assert(personsSet.size() == 2);
    personsSet.insert(c);
    assert(personsSet.size() == 3);
    personsSet.insert(d);
    assert(personsSet.size() == 4);
    personsSet.erase(b);
    assert(personsSet.size() == 3);
    personsSet.erase(e);
    assert(personsSet.size() == 3);

    map<Person, string> personsMap;
    personsMap.insert({ a, "first" });
    personsMap.insert({ b, "second" });
    personsMap.insert({ c, "third" });
    personsMap.insert({ d, "fourth" });
    assert(personsMap.size() == 4);
    personsMap[d] = "";
    assert(personsMap[d] == "");
    personsMap.erase(b);
    assert(personsMap.size() == 3);
    personsMap.erase(e);
    assert(personsMap.size() == 3);

    unordered_set<Person, Person> personUset;
    personUset.insert(a);
    assert(personUset.size() == 1);
    personUset.insert(b);
    assert(personUset.size() == 2);
    personUset.insert(c);
    assert(personUset.size() == 3);
    auto f = personUset.find(b);
    personUset.erase(f);
    assert(personUset.size() == 2);
    f = personUset.find(e);
    assert(f == personUset.end());

    unordered_map<Person, int, Person> personUmap;
    personUmap[b] = 2;
    assert(personUmap.size() == 1);
    assert(personUmap[b] == 2);
    personUmap[c] = 3;
    personUmap[d] = 4;
    auto mf = personUmap.find(c);
    assert(mf->first == Person({"c", 40, 1000.0}));
    assert(mf->second == 3);
    assert(personUmap.size() == 3);
    personUmap.erase(mf);
    assert(personUmap.size() == 2);
    personUmap.erase(e);
    assert(personUmap.size() == 2);
}