web-dev-qa-db-ja.com

「タプル」と「タイ」を介して比較演算子を実装するのは良い考えですか?

(注:Tupleおよびtieは、BoostまたはC++ 11から取得できます。)
2つの要素のみを含む小さな構造体を作成するとき、厳密な弱順序のためのstd::pairのように、そのデータ型に対してすべての重要な処理が既に行われているため、operator<を選択する傾向があります。
欠点は、ほとんど役に立たない変数名です。たとえ自分でtypedefを作成したとしても、2日後にfirstsecondが正確に何であったか、特に両方が同じタイプである場合は思い出せません。 pairsをネストするとかなりひどいので、これは3つ以上のメンバーではさらに悪化します。
そのためのもう1つのオプションは、BoostまたはC++ 11のTupleですが、実際には見栄えがよく明確ではありません。そこで、必要な比較演算子を含め、構造体を自分で書くことに戻ります。
特に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は、渡された引数からT&Tuple参照を作成します。)


編集Tupleをプライベートに継承する@DeadMGからの提案は悪いものではありませんが、かなりの欠点があります。

  • オペレーターが独立している場合(おそらく友人)、私は公に継承する必要があります
  • キャストすると、関数/演算子(具体的にはoperator=)を簡単にバイパスできます
  • tieソリューションを使用すると、特定のメンバーが順序に関係ない場合は除外できます

この実装に考慮する必要がある欠点はありますか?

95
Xeo

これにより、正しい演算子を自分で作成するよりも簡単に作成できるようになります。プロファイリングで比較操作がアプリケーションの時間のかかる部分であることが示されている場合にのみ、別のアプローチを検討してください。さもなければ、これを維持することの容易さは、可能なパフォーマンスの懸念を上回るはずです。

58
Mark B

私はこの同じ問題に遭遇し、私のソリューションはc ++ 11可変長テンプレートを使用しています。コードは次のとおりです。

.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
);
5
user2369060

私の意見では、あなたはまだstd::Tuple解決策と同じ問題に取り組んでいない、つまり、各メンバー変数の数と名前の両方を知る必要があり、関数でそれを2回複製している。 private継承を選択できます。

struct somestruct : private std::Tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

このアプローチはlittle少し複雑ですが、オーバーロードするすべての演算子のすべての場所ではなく、1つの場所で変数と名前を維持するだけです。

3
Puppy

複数の演算子オーバーロード、またはTupleの複数のメソッドを使用する場合は、Tupleをクラスのメンバーにするか、Tupleから派生させることをお勧めします。そうでなければ、あなたがしていることはもっと多くの仕事です。 2つの間で決定するとき、答えるべき重要な質問は次のとおりです。クラスにbeタプルを追加しますか?そうでない場合は、タプルを含め、委任を使用してインターフェイスを制限することをお勧めします。

タプルのメンバーを「名前変更」するためのアクセサーを作成できます。

1
Lee Louviere