Эффективные и простые операторы сравнения для структур
Приложение, в котором я работаю, в настоящее время имеет большое количество структур, которые содержат данные, которые поступают из различных источников, таких как базы данных и файлы. Например, например:
struct A
{
float val1;
std::string val2;
int val3;
bool operator < (const A& other) const;
};
Для обработки эти структуры хранятся в STL-контейнерах, таких как карты, и поэтому им нужен оператор сравнения. Все они одинаковы и с использованием простой логической логики они могут быть написаны так:
bool A:operator < (const A& o) const {
return val1 < o.val1 ||
(val1 == o.val1 && ( val2 < o.val2 ||
(val2 == o.val2 && ( val3 < o.val3 ) ) );
}
Это кажется эффективным, но имеет несколько недостатков:
- Эти выражения становятся огромными, если структуры составляют десяток или более членов.
- Грубо писать и поддерживать, если члены меняются.
- Это необходимо для каждой структуры отдельно.
Есть ли более удобный способ сравнения таких структур?
Ответы
Ответ 1
Вы можете использовать встроенное сравнение, которое поставляется с <tuple>
следующим образом:
#include <tuple>
bool A::operator < (const A& rhs) const {
return std::tie(val1, val2, val3) < std::tie(rhs.val1, rhs.val2, rhs.val3);
}
Это не масштабируется, когда к структуре добавляется все больше и больше членов данных, но это также может быть намеком на то, что вы могли бы создавать промежуточные структуры, которые реализуют operator <
и, следовательно, хорошо играют с вышеупомянутой реализацией оператора верхнего уровня operator <
.
Позвольте мне добавить еще три комментария к operator <
.
-
Когда у вас есть operator <
, клиенты будут ожидать, что все другие операторы сравнения также будут предоставлены. Прежде чем иметь трехстороннее сравнение в С++ 20, вы можете избежать ненужного кода шаблона, например, используя библиотеку операторов Boost:
#include <boost/operators.hpp>
struct A : private boost::totally_ordered<A> { /* ... */ };
который генерирует все операторы, основанные на operator <
и operator ==
для вас.
-
В вашем примере нет необходимости, чтобы оператор был членом A
Вы можете сделать его свободной функцией, что предпочтительнее (см. Здесь для обоснования).
-
Если нет никакого внутреннего порядка, связанного с A
и вам просто нужен operator <
для хранения экземпляров в виде ключей на std::map
, подумайте о предоставлении именованного предиката.
Ответ 2
Отличный ответ lubgr.
Еще одно уточнение, которое я выполняю, - это создание функции-члена as_tuple
для любого объекта, который должен быть упорядочен его членами:
#include <string>
#include <tuple>
#include <iostream>
struct A
{
float val1;
std::string val2;
int val3;
// provide easy conversion to tuple
auto as_tuple() const
{
return std::tie(val1, val2, val3);
}
};
Который часто вызывает мысли об общей системе создания объектов и кортежей, взаимозаменяемых с точки зрения сравнений
template<class T> auto as_tuple(T&& l) -> decltype(l.as_tuple())
{
return l.as_tuple();
}
template<class...Ts>
auto as_tuple(std::tuple<Ts...> const& tup)
-> decltype(auto)
{
return tup;
}
template<class L, class R>
auto operator < (L const& l, R const& r)
-> decltype(as_tuple(l), void(), as_tuple(r), void(), bool())
{
return as_tuple(l) < as_tuple(r);
}
Что позволяет такой код, как:
int main()
{
auto a = A { 1.1, "foo", 0 };
auto b = A { 1.1, "foo", 1 };
auto test1 = a < b;
std::cout << test1 << std::endl;
auto test2 = a < std::make_tuple(1.1, "bar", 0);
std::cout << test2 << std::endl;
auto test3 = std::make_tuple(1.0, "bar", 0) < std::make_tuple(1.1, "bar", 0);
std::cout << test3 << std::endl;
auto test4 = a < std::make_tuple(2l, std::string("bar"), 0);
std::cout << test4 << std::endl;
}
пример: http://coliru.stacked-crooked.com/a/ead750f3f65e3ee9