クラス階層の operator==をオーバーロードする正しい方法は何ですか?

この種の階層については、Scott Meyer の効果的な C++ のアドバイスに従い、具体的な基本クラスを持たないようにします。いずれにせよ、あなたはこれを行っているようです。

operator== を実装します 具体的なリーフノードクラスタイプのみの無料関数として、おそらく友達です。

基本クラスにデータ メンバーが必要な場合は、基本クラスに (おそらく保護された) 非仮想ヘルパー関数を提供します (isEqual 、たとえば) 派生クラスの operator==

bool operator==(const B& lhs, const B& rhs)
{
    return lhs.isEqual( rhs ) && lhs.bar == rhs.bar;
}

operator== を避けることによって 抽象基本クラスで機能し、比較関数を保護することで、型の異なる 2 つのオブジェクトの基本部分のみが比較されるクライアント コードで誤ってフォールバックが発生することはありません。

dynamic_cast を使用して仮想比較関数を実装するかどうかはわかりません。 、私はこれを行うのは気が進まないでしょうが、その必要性が証明されている場合は、おそらく基本クラスの純粋仮想関数を使用します (not operator== ) operator== を使用して、具体的な派生クラスでこのようなものとしてオーバーライドされました 派生クラス用。

bool B::pubIsEqual( const A& rhs ) const
{
    const B* b = dynamic_cast< const B* >( &rhs );
    return b != NULL && *this == *b;
}

先日も同じ問題が発生し、次の解決策を思いつきました:

struct A
{
    int foo;
    A(int prop) : foo(prop) {}
    virtual ~A() {}
    virtual bool operator==(const A& other) const
    {
        if (typeid(*this) != typeid(other))
            return false;

        return foo == other.foo;
    }
};

struct B : A
{
    int bar;
    B(int prop) : A(1), bar(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return bar == static_cast<const B&>(other).bar;
    }
};

struct C : A
{
    int baz;
    C(int prop) : A(1), baz(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return baz == static_cast<const C&>(other).baz;
    }
};

これについて私が気に入らないのは、typeid チェックです。それについてどう思いますか?


キャストを使用したくなく、B のインスタンスと C のインスタンスを誤って比較しないようにする場合は、Scott Meyers が「より効果的な C++」の項目 33 で提案している方法でクラス階層を再構築する必要があります。実際、このアイテムは代入演算子を扱っていますが、関連のない型に使用すると意味がありません。比較操作の場合、B のインスタンスと C を比較するときに false を返すのは理にかなっています。

以下は、RTTI を使用し、クラス階層を具体的なリーフと抽象ベースに分割しないサンプル コードです。

このサンプル コードの良い点は、関連のないインスタンス (B と C など) を比較するときに std::bad_cast を取得しないことです。それでも、コンパイラは、必要に応じてそれを行うことができます。同じ方法で operator<を実装し、さまざまな A、B、および C インスタンスのベクトルをソートするために使用できます。

ライブ

#include <iostream>
#include <string>
#include <typeinfo>
#include <vector>
#include <cassert>

class A {
    int val1;
public:
    A(int v) : val1(v) {}
protected:
    friend bool operator==(const A&, const A&);
    virtual bool isEqual(const A& obj) const { return obj.val1 == val1; }
};

bool operator==(const A& lhs, const A& rhs) {
    return typeid(lhs) == typeid(rhs) // Allow compare only instances of the same dynamic type
           && lhs.isEqual(rhs);       // If types are the same then do the comparision.
}

class B : public A {
    int val2;
public:
    B(int v) : A(v), val2(v) {}
    B(int v, int v2) : A(v2), val2(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const B&>(obj); // will never throw as isEqual is called only when
                                              // (typeid(lhs) == typeid(rhs)) is true.
        return A::isEqual(v) && v.val2 == val2;
    }
};

class C : public A {
    int val3;
public:
    C(int v) : A(v), val3(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const C&>(obj);
        return A::isEqual(v) && v.val3 == val3;
    }
};

int main()
{
    // Some examples for equality testing
    A* p1 = new B(10);
    A* p2 = new B(10);
    assert(*p1 == *p2);

    A* p3 = new B(10, 11);
    assert(!(*p1 == *p3));

    A* p4 = new B(11);
    assert(!(*p1 == *p4));

    A* p5 = new C(11);
    assert(!(*p4 == *p5));
}