Реализация операторов сравнения через 'кортеж ' и' галстук', хорошая идея?


(Примечание: tuple и tie можно взять из Boost или C++11.)
При написании небольших структур только с двумя элементами, я иногда склонен выбирать std::pair, поскольку все важные вещи уже сделаны для этого типа данных, например operator< для строгого слабого заказа.
Недостатками, однако, являются довольно бесполезные имена переменных. Даже если я сам создал это typedef, я не буду помнить 2 дня спустя, что first и что second точно, особенно если они оба одного типа. Это становится еще хуже для более, чем двух членов, как гнездовье pairs в значительной степени отстой.
Другой вариант для этого является 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 of T& ссылки из переданных аргументов.)


Edit: предложение от @DeadMG в частном порядке наследовать от tuple это не плохо, но он получил довольно некоторые недостатки:

  • если операторы свободны (возможно, друзья), мне нужно наследовать публично
  • С кастинг, мои функции / операторы (operator= в частности) можно легко обойти
  • С tie решение, я могу оставить некоторые члены, если они не имеют значения для заказа

есть ли какие-либо недостатки в этой реализации, которые мне нужно рассмотреть?

4 84

4 ответа:

Это, безусловно, сделает его легче написать правильный оператор, чем прокатки его самостоятельно. Я бы сказал, что только рассмотрите другой подход, если профилирование показывает, что операция сравнения является трудоемкой частью вашего приложения. В противном случае простота поддержания этого должна перевешивать любые возможные проблемы с производительностью.

Я пришел через эту же проблему и мое решение использует C++11 вариативные шаблоны. Вот код:

The .ч. ч.:

/***
 * 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
};

Это мало немного больше беспорядка для начала, но вы только поддерживаете переменные и имена в одном месте, а не в каждом месте для каждого оператора, который вы хотите перегрузить.

Если вы планируете использовать более чем одну перегрузку оператора или несколько методов из кортежа, я бы рекомендовал сделать кортеж членом класса или производным от кортежа. В противном случае, то, что вы делаете намного больше работы. При выборе между ними, важный вопрос, чтобы ответить: вы хотите, чтобы ваш класс быть кортеж? Если нет, я бы рекомендовал содержать кортеж и ограничивать интерфейс с помощью делегирования.

вы можете создать методы доступа для "переименования" членов кортеж.