Reputation: 9936
I found that there are different ways to define custom compare functions for a user defined object. I would like to know the things that I should take into account before choosing one over another.
If I have student object, I can write a custom compare function in the following ways.
struct Student
{
string name;
uint32_t age;
// Method 1: Using operator <
bool operator<(const Student& ob)
{
return age < ob.age;
}
};
// Method 2: Custom Compare Function
bool compStudent(const Student& a, const Student& b)
{
return a.age < b.age;
}
// Method 3: Using operator ()
struct MyStudComp
{
bool operator() (const Student& a, const Student& b)
{
return a.age < b.age;
}
}obComp;
To sort a vector of students I can use either of the below methods.
vector<Student> studs; // Consider I have this object populated
std::sort(studs.begin(), studs.end()); // Method 1
std::sort(studs.begin(), studs.end(), compStudent); // Method 2
std::sort(studs.begin(), studs.end(), obComp); // Method 3
// Method 4: Using Lambda
sort(studs.begin(), studs.end(),
[](const Student& a, const Student& b) -> bool
{
return a.age < b.age;
});
How are these methods different and how should I decide between these. Thanks in advance.
Upvotes: 2
Views: 140
Reputation: 69864
How are these methods different and how should I decide between these.
They differ in their implicit statements of intent. You should use the form that expresses your intent most succinctly.
Relying on operator<
implies to someone reading your code that your objects are implicitly ordered, like numbers or strings. They ought to be things that people would say, "well obviously x comes before y".
If the ordering of the map is more abstract, then an ordering function might be better because it expresses the idea that you are imposing an order on the map which may not be a natural order.
in the example you give, I might choose to express the intent in either a function object called ageIsLess
for example. As a reader of code using the map is now fully aware of intent.
For example:
#include <cstdint>
#include <set>
#include <string>
#include <algorithm>
#include <iterator>
struct Student
{
std::string name;
std::uint32_t age;
};
struct ByAscendingAge
{
bool operator() (const Student& a, const Student& b) const
{
return a.age < b.age;
}
};
bool age_is_less(const Student& l, const Student& r)
{
return l.age < r.age;
};
bool name_is_less(const Student& l, const Student& r)
{
return l.name < r.name;
};
int main()
{
// this form expresses the intent that any 2 different maps of this type can have different ordering
using students_by_free_function = std::set<Student, bool (*)(const Student&, const Student&)>;
// ordered by age
students_by_free_function by_age_1(age_is_less);
// ordered by name
students_by_free_function by_name_1(name_is_less);
// above two maps are the same type so we can assign them, which implicitly reorders
by_age_1 = by_name_1;
// this form expresses the intent that the ordering is a PROPERTY OF THIS TYPE OF SET
using students_by_age = std::set<Student, ByAscendingAge>;
// note that we don't need a comparator in the constructor
students_by_age by_age_2;
// by_age_2 = by_age_1; // not allowed because the sets are a different type
// but we can assign iterator ranges of course
std::copy(std::begin(by_age_1),
std::end(by_age_1),
std::inserter(by_age_2,
std::end(by_age_2)));
}
Upvotes: 0
Reputation: 1218
The performance between the different methods is not very different, however, using <
will let you be more flexible, and makes using built-ins much easier. I also think using ()
is kind of weird.
The bigger issue in your example is that your methods should be using const refs instead of values. I.e. bool operator<(Student ob)
could be friend bool operator<(const Student& ls, const Student& rs){...}
. Also, see here for some examples of different things to consider when overloading operators.
Upvotes: 2
Reputation: 3355
There really is no "right" way per se, but if it makes sense for your object to have custom comparators (i.e. operator<
etc.) then it would be wise to simply use those. However you may want to sort your object based on a different field member and so providing a custom lambda based on those field comparisons would make sense in that case.
For example, your Student
class currently uses an overloaded operator<
to compare student ages, so if you are sorting a container of Student
s based on age then just use this operator implicitly. However, you may want (at another time) to sort based on the names so in this case you could provide a custom lambda as the most elegant method:
std::vector<Student> vec;
// populate vec
std::sort(vec.begin(), vec.end(), [](auto& lhs, auto& rhs) { return lhs.name < rhs.name; });
where the student names are sorted via lexicographical comparisons.
Upvotes: 0
Reputation: 31467
The performance is not going to be noticably different. But it's convenient (and expected) in many cases to have a operator<
, so I'd go for that over the special compare function.
Upvotes: 0