C ++ 20では、デフォルトの比較、別名「スペースシップ」がoperator<=>
導入されました。これにより、コンパイラで生成された<
/ <=
/ ==
/ !=
/ >=
/および/または>
明白な/ナイーブ(?)実装の演算子を要求できます。
auto operator<=>(const MyClass&) const = default;
...ただし、より複雑な状況に合わせてカスタマイズできます(以下で説明します)。正当化と議論を含む言語提案については、ここを参照してください。operator<=>
この回答は、C ++ 17以前、および...の実装をいつカスタマイズする必要があるかについての洞察に引き続き関連しています。
これを以前に標準化していないことはC++にとって少し役に立たないように思われるかもしれませんが、多くの場合、構造体/クラスには、比較から除外するデータメンバー(カウンター、キャッシュされた結果、コンテナー容量、最後の操作の成功/エラーコード、カーソルなど)があります。また、以下を含むがこれらに限定されない無数の事柄について行う決定。
- どのフィールドを最初に比較するか。たとえば、特定の
int
メンバーを比較すると、99%の不均等なオブジェクトがすぐに削除される可能性がありますが、map<string,string>
メンバーは同じエントリを持っていることが多く、比較するのに比較的費用がかかる可能性があります。値が実行時に読み込まれる場合、プログラマーは洞察を得ることができます。コンパイラはおそらくできません
- 文字列の比較:大文字と小文字の区別、空白と区切り文字の同等性、エスケープ規則..
- フロート/ダブルを比較するときの精度
- NaN浮動小数点値が等しいと見なされるべきかどうか
- ポインターまたはデータへのポインターの比較(後者の場合、ポインターが配列を指しているかどうか、および比較が必要なオブジェクト/バイトの数を知る方法)
- 並べ替えられていないコンテナ(たとえば
vector
、list
)を比較するときに順序が重要かどうか、重要な場合は、比較する前にそれらをインプレースで並べ替えてもよいか、比較が行われるたびに追加のメモリを使用して一時的なコンテナを並べ替えてもよいかどうか
- 比較する必要のある有効な値を現在保持している配列要素の数(サイズはどこかにありますか、それとも番兵ですか?)
union
比較するaのメンバー
- 正規化:たとえば、日付タイプでは範囲外の日または月が許可される場合があります。または、有理/分数オブジェクトの6/8が、別のオブジェクトの3/4がある場合があります。これは、パフォーマンス上の理由から修正されます。別の正規化ステップで怠惰に; 比較する前に、正規化をトリガーするかどうかを決定する必要がある場合があります
- 弱ポインタが有効でない場合の対処方法
- 自分自身を実装していないメンバーとベースを処理する方法
operator==
(ただし、compare()
またはoperator<
またはstr()
またはゲッターを持っている可能性があります...)
- 他のスレッドが更新する可能性のあるデータの読み取り/比較中に取得する必要のあるロック
したがって、コンパイルさせても実行時に意味のある結果が得られないようにするのではなく、特定の構造に対して比較が何を意味するのかを明示的に考えるまで、エラーが発生するのは良いことです。
bool operator==() const = default;
そうは言っても、「ナイーブな」メンバーごとの==
テストが大丈夫だと判断したときに、C++で言わせていただければ幸いです。についても同じです!=
。複数のメンバー/ベースを考えると、「デフォルト」、、、<
および実装は絶望的ですが、宣言の順序に基づいてカスケードすることは可能ですが、メンバーの順序付けの命令が矛盾していることを考えると、望ましいものになる可能性はほとんどありません(ベースは必ずメンバーの前にあり、アクセシビリティ、依存使用前の構築/破壊)。より広く役立つためには、C ++は、選択をガイドするための新しいデータメンバー/ベースアノテーションシステムを必要とします-それは、理想的にはASTベースのユーザー定義コード生成と組み合わせて、標準で持つのは素晴らしいことです...私は期待していますそれ'<=
>
>=
等式演算子の典型的な実装
もっともらしい実装
合理的で効率的な実装は次のようになる可能性があります。
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
operator==
これにもも必要であることに注意してくださいMyStruct2
。
この実装の意味と代替案については、以下のMyStruct1の詳細に関するディスカッションの見出しで説明します。
==、<、><=などへの一貫したアプローチ
std::tuple
の比較演算子を利用して、独自のクラスインスタンスを比較するのは簡単です。これを使用std::tie
して、フィールドへの参照のタプルを目的の比較順序で作成するだけです。ここから私の例を一般化する:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) ==
std::tie(rhs.my_struct2, rhs.an_int);
}
inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) <
std::tie(rhs.my_struct2, rhs.an_int);
}
// ...etc...
比較したいクラスを「所有」する場合(つまり、編集可能、企業およびサードパーティのライブラリの要素)、特にreturn
ステートメントから関数の戻り型を推測するC ++ 14の準備ができている場合は、「」を追加する方がよい場合がよくあります。比較できるようにしたいクラスに"メンバー関数を結び付けます。
auto tie() const { return std::tie(my_struct1, an_int); }
次に、上記の比較は次のように簡略化されます。
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
より完全な比較演算子のセットが必要な場合は、ブースト演算子をお勧めします(を検索してless_than_comparable
ください)。何らかの理由で不適切な場合は、サポートマクロ(オンライン)のアイデアが好きかどうかはわかりません。
#define TIED_OP(STRUCT, OP, GET_FIELDS) \
inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
{ \
return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
}
#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
TIED_OP(STRUCT, ==, GET_FIELDS) \
TIED_OP(STRUCT, !=, GET_FIELDS) \
TIED_OP(STRUCT, <, GET_FIELDS) \
TIED_OP(STRUCT, <=, GET_FIELDS) \
TIED_OP(STRUCT, >=, GET_FIELDS) \
TIED_OP(STRUCT, >, GET_FIELDS)
...それからそれを使用することができます...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(C ++ 14メンバータイバージョンはこちら)
MyStruct1の詳細についてのディスカッション
自立型とメンバーを提供するという選択には影響がありoperator==()
ます...
独立した実装
あなたには興味深い決断があります。クラスは暗黙的にから構築できるためMyStruct2
、独立型/非メンバーbool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
関数は...をサポートします。
my_MyStruct2 == my_MyStruct1
...最初にMyStruct1
から一時的なものを作成しmy_myStruct2
、次に比較を行います。MyStruct1::an_int
これにより、コンストラクターのデフォルトのパラメーター値である。に確実に設定されたままになります-1
。an_int
の実装に比較を含めるかどうかに応じてoperator==
、それ自体がのメンバーと同等に比較されるものと同等に比較MyStruct1
される場合とされない場合があります。さらに、一時的なものを作成することは、既存のメンバーを一時的なものにコピーし、比較後に破棄するだけなので、非常に非効率的な操作になる可能性があります。(もちろん、コンストラクターを作成するか、のデフォルト値を削除することで、比較のためにsが暗黙的に作成されるのを防ぐことができます。)MyStruct2
MyStruct1
my_struct_2
MyStruct1
my_struct2
MyStruct1
explicit
an_int
メンバーの実装
MyStruct1
からの暗黙の構築を避けたい場合はMyStruct2
、比較演算子をメンバー関数にします。
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
const
キーワード(メンバーの実装にのみ必要)は、オブジェクトを比較してもオブジェクトは変更されないため、オブジェクトで許可できることをコンパイラーに通知することに注意してくださいconst
。
目に見える表現の比較
必要な種類の比較を取得する最も簡単な方法は、次の場合があります...
return lhs.to_string() == rhs.to_string();
...これもしばしば非常に高価です-それらstring
はただ捨てられるために痛々しいほど作成されました!浮動小数点値を持つ型の場合、表示される表現を比較すると、表示される桁数によって、比較中にほぼ等しい値が等しいものとして扱われる許容範囲が決まります。