Реализация операторов сравнения через 'tuple' и 'tie', хорошая идея?
(Заметка: tuple
а также tie
можно взять из Boost или C++11.)
При написании небольших структур только с двумя элементами, я иногда склонен выбирать std::pair
, поскольку все важные вещи уже сделаны для этого типа данных, как operator<
для строгого-слабого заказа.
Недостатками являются практически бесполезные имена переменных. Даже если бы я сам создал это typedef
Через 2 дня не вспомню что first
и что second
точно было, особенно если они оба одного типа. Это становится еще хуже для более чем двух членов, так как вложенность pair
довольно много отстой.
Другой вариант для этого является tuple
либо из Boost, либо из C++11, но это не выглядит лучше и понятнее. Поэтому я возвращаюсь к написанию структур самостоятельно, включая любые необходимые операторы сравнения.
Поскольку особенно operator<
может быть довольно громоздким, я думал обойти весь этот беспорядок, просто полагаясь на операции, определенные для tuple
:
Пример operator<
например, для строгого-слабого порядка:
bool operator<(MyStruct const& lhs, MyStruct const& rhs){
return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}
(tie
делает tuple
из T&
ссылки из переданных аргументов.)
Изменить: предложение @DeadMG о частном наследовании от tuple
неплохой, но у него есть некоторые недостатки:
- Если операторы стоят отдельно (возможно, друзья), мне нужно публично наследовать
- С кастингом, мои функции / операторы (
operator=
в частности) можно легко обойти - С
tie
Решение, я могу опустить некоторых членов, если они не имеют значения для заказа
Есть ли какие-либо недостатки в этой реализации, которые я должен рассмотреть?
4 ответа
Это, безусловно, облегчит написание правильного оператора, чем выполнение его самостоятельно. Я бы сказал, что рассмотрим только другой подход, если профилирование показывает, что операция сравнения является трудоемкой частью вашего приложения. В противном случае простота поддержания этого должна перевесить любые возможные проблемы производительности.
Я столкнулся с этой же проблемой, и мое решение использует шаблоны с ++11 variadic. Вот код:
Часть.h:
/***
* Generic lexicographical less than comparator written with variadic templates
* Usage:
* pass a list of arguments with the same type pair-wise, for intance
* lexiLessthan(3, 4, true, false, "hello", "world");
*/
bool lexiLessthan();
template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
if (first != second)
{
return first < second;
}
else
{
return lexiLessthan(rest...);
}
}
И.cpp для базового случая без аргументов:
bool lexiLessthan()
{
return false;
}
Теперь ваш пример становится:
return lexiLessthan(
lhs.one_member, rhs.one_member,
lhs.another, rhs.another,
lhs.yet_more, rhs.yet_more
);
На мой взгляд, вы все еще не решаете ту же проблему, что и std::tuple
решает, а именно: вам нужно знать как количество, так и имя каждой переменной-члена, вы дублируете ее дважды в функции. Вы можете выбрать private
наследование.
struct somestruct : private std::tuple<...> {
T& GetSomeVariable() { ... }
// etc
};
Этот подход для начала немного сложнее, но вы храните переменные и имена только в одном месте, а не в каждом месте для каждого оператора, который вы хотите перегрузить.
Если вы планируете использовать более одной перегрузки оператора или несколько методов из кортежа, я бы рекомендовал сделать кортеж членом класса или наследовать от кортежа. В противном случае то, что вы делаете, - это гораздо больше работы. При выборе между ними важен вопрос: хотите ли вы, чтобы ваш класс был кортежем? Если нет, я бы порекомендовал содержать кортеж и ограничить интерфейс с помощью делегирования.
Вы можете создавать средства доступа, чтобы "переименовывать" членов кортежа.