web-dev-qa-db-ja.com

C ++コンパイラがoperator ==とoperator!=を定義しないのはなぜですか?

私は、コンパイラーに可能な限り多くの作業を行わせることが大好きです。単純なクラスを作成する場合、コンパイラは「無料」で以下を提供できます。

  • デフォルト(空の)コンストラクター
  • コピーコンストラクター
  • デストラクタ
  • 代入演算子(operator=

ただし、operator==operator!=などの比較演算子を提供するようには見えません。例えば:

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

これには正当な理由がありますか?メンバーごとの比較の実行が問題になるのはなぜですか?クラスがメモリを割り当てる場合は、注意する必要がありますが、単純なクラスの場合、コンパイラがこれを行うことができますか?

281
Rob

コンパイラーは、ポインター比較または深い(内部)比較のどちらが必要かを知りません。

それを実装しないで、プログラマに自分でそれをさせるほうが安全です。その後、彼らは好きなすべての仮定を行うことができます。

68
Mark Ingram

コンパイラがデフォルトのコピーコンストラクタを提供できる場合、同様のデフォルトのoperator==()を提供できるはずであるという議論は、ある程度の意味をなします。この演算子にコンパイラーが生成したデフォルトを提供しないという決定の理由は、Stroustrupが「C++の設計と進化」(セクション11.4.1-コピーの制御)でデフォルトのコピーコンストラクターについて述べたことによって推測できると思います。 :

個人的には、コピー操作がデフォルトで定義されており、多くのクラスのオブジェクトのコピーを禁止していることが残念です。ただし、C++はデフォルトの割り当てを継承し、Cからコンストラクタをコピーし、頻繁に使用されます。

そのため、「C++にデフォルトのoperator==()がないのはなぜですか」という質問の代わりに、「C++にデフォルトの割り当てとコピーコンストラクタがあるのはなぜですか」と答えるべきでした。 Cとの後方互換性のため(おそらくC++のいぼのほとんどの原因ですが、おそらくC++の人気の主な理由)。

私の目的のために、私のIDEでは、新しいクラスに使用するスニペットにプライベート割り当て演算子とコピーコンストラクターの宣言が含まれているため、新しいクラスを生成するときにデフォルトの割り当てとコピー操作はありません-Iコンパイラーがそれらを生成できるようにするには、private:セクションからこれらの操作の宣言を明示的に削除する必要があります。

292
Michael Burr

私見、「良い」理由はありません。この設計決定に同意する人が非常に多いのは、値ベースのセマンティクスの力をマスターすることを学ばなかったためです。人々は実装で生のポインタを使用するため、多くのカスタムコピーコンストラクタ、比較演算子、デストラクタを記述する必要があります。

適切なスマートポインター(std :: shared_ptrなど)を使用する場合、デフォルトのコピーコンストラクターは通常問題なく、仮想のデフォルトの比較演算子の明らかな実装も同様に問題ありません。

42
alexk7

これは、C++が==を実行しなかったためにC =を実行しなかったと回答したためです。 Cはシンプルにしたかった:C実装= memcpy;ただし、==はパディングのためmemcmpで実装できません。パディングは初期化されていないため、memcmpは、同じであっても異なると言います。空のクラスにも同じ問題があります。memcmpは、空のクラスのサイズがゼロではないため、それらが異なると言います。上記から、Cで==を実装するよりも==を実装する方が複雑であることがわかります。これに関するコード 私が間違っている場合、あなたの訂正は大歓迎です。

36
Rio Wing

この video で、STLの作成者であるアレックスステパノフは、13:00頃にまさにこの質問に答えます。要約すると、C++の進化を見て、次のように主張しています。

  • ==および!=が暗黙的に宣言されていないことは残念です(そしてBjarneは彼に同意します)。正しい言語には、それらのものが用意されている必要があります(彼はさらに、!=を定義して、 ==
  • その理由は、Cに(C++の多くの問題のように)ルーツがあるためです。そこで、代入演算子はbit by bit assignmentで暗黙的に定義されていますが、うまくいきません。 ==より詳細な説明は、Bjarne Stroustrupの記事 article にあります。
  • フォローアップの質問で、なぜメンバー比較によるメンバーではなかったのですか彼は驚くべきことを言います:Cは一種の自国語であり、リッチーのためにこれらのものを実装している人は、これを実装するのが難しいと彼に言った!

そして、彼は(遠い)将来==!=は暗黙的に生成されます。

24

デフォルトの==を定義することはできませんが、通常は自分で定義する必要がある!=を介してデフォルトの==を定義できます。このためには、次のことを行う必要があります。

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

詳細については、 http://www.cplusplus.com/reference/std/utility/rel_ops/ を参照してください。

さらに、operator<を定義する場合、std::rel_opsを使用すると、<=、>、> =の演算子をそこから推定できます。

ただし、std::rel_opsを使用する場合は注意が必要です。比較演算子は、予期しない型に対して推測できるためです。

基本演算子から関連演算子を推測するより好ましい方法は、 boost :: operators を使用することです。

Boostで使用されるアプローチは、スコープ内のすべてのクラスではなく、必要なクラスの演算子の使用法を定義するため、より優れています。

「+ =」、「-=」などから「+」を生成することもできます(完全なリストを参照してください here

15
sergtk

C++ 20は、デフォルトの比較演算子を簡単に実装する方法を提供します。

cppreference.com の例:

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>
14
Ville-Valtteri

C++ 0x 持っている デフォルト関数の提案がありましたので、あなたはdefault operator==;と言うことができました。

10
MSalters

概念的には、平等を定義するのは簡単ではありません。 PODデータの場合でも、フィールドが同じであっても、異なるオブジェクト(異なるアドレス)である場合でも、必ずしも等しいとは言えません。これは、実際には演算子の使用法に依存します。残念ながら、あなたのコンパイラは精神的なものではなく、それを推測することはできません。

これに加えて、デフォルトの機能は、自分自身を足で撃つ優れた方法です。説明するデフォルトは、基本的にPOD構造体との互換性を保つためにあります。ただし、開発者がそれらを忘れたり、デフォルトの実装のセマンティクスを忘れるなど、十分な混乱を引き起こします。

5
Paul de Vrieze

これには正当な理由がありますか?メンバーごとの比較の実行が問題になるのはなぜですか?

機能的には問題にならないかもしれませんが、パフォーマンスの観点から、デフォルトのメンバーごとの比較は、デフォルトのメンバーごとの割り当て/コピーよりも準最適になる可能性があります。割り当ての順序とは異なり、比較の順序は、最初の等しくないメンバーが残りをスキップできることを意味するため、パフォーマンスに影響を与えます。したがって、通常等しいメンバーがいくつかある場合は、それらを最後に比較する必要があり、コンパイラーはどのメンバーが等しい可能性が高いかを知りません。

この例を考えてみましょう。verboseDescriptionは、天気予報の比較的小さなセットから選択された長い文字列です。

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(もちろん、コンパイラーは、副作用がないと認識した場合、比較の順序を無視する権利がありますが、それ自体のより良い情報がないソースコードからは、依然としてqueを取得します。)

1
Museful