1

(潜在的に) 多くのサブクラスを持つ基本クラスを持っています。基本クラスの任意の 2 つのオブジェクトが等しいかどうかを比較できるようにしたいと考えています。冒涜的な typeid キーワードを呼び出さずにこれを実行しようとしています。

#include <iostream>

struct Base {

    virtual bool operator==(const Base& rhs) const
        {return rhs.equalityBounce(this);}

    virtual bool equalityBounce(const Base* lhs) const = 0;
    virtual bool equalityCheck(const Base* lhs) const = 0;
};

struct A : public Base {

    A(int eh) : a(eh) {}
    int a;

    virtual bool equalityBounce(const Base* lhs) const{
        return lhs->equalityCheck(this);
    }

    virtual bool equalityCheck(const Base* rhs) const {return false;}
    virtual bool equalityCheck(const A* rhs) const {return a == rhs->a;}
};


int main() {

    Base *w = new A(1), *x = new A(2);

    std::cout << (*w == *w) << "\n";
    std::cout << (*w == *x) << "\n";
}

equalityBounce() の lhs が Base* であるため、記述されたコードが失敗していることを理解しています。そのため、A* を受け取る equalityCheck() のバージョンについても認識していません。しかし、私はそれについて何をすべきかわかりません。

4

3 に答える 3

1

うまくいかない理由

二重ディスパッチの実装の問題は、最も具体的なものequalityCheck()が呼び出されることを期待していることです。

しかし、実装は完全にポリモーフィックな基本クラスに基づいており、equalityCheck(const A*)オーバーロードしますが、オーバーライドしません equalityCheck(const Base*)!

別の言い方をすれば、コンパイル時にコンパイラーはA::equalityBounce()が呼び出される可能性があることを認識していますがequalityCheck(A*)(thisは であるためA*)、残念ながらパラメーターBase::equalityCheck()に特化したバージョンを持たない呼び出しを行いA*ます。

実装方法は?

double ディスパッチが機能するには、基本クラスに double ディスパッチされた equalityCheck() の型固有の実装が必要です。

これが機能するには、ベースがその子孫を認識している必要があります。

struct A; 

struct Base {

    virtual bool operator==(const Base& rhs) const
    {
        return rhs.equalityBounce(this);
    }

    virtual bool equalityBounce(const Base* lhs) const = 0;
    virtual bool equalityCheck(const Base* lhs) const = 0;
    virtual bool equalityCheck(const A* lhs) const = 0;
};

struct A : public Base {
    ...
    bool equalityBounce(const Base* lhs) const override{  
        return lhs->equalityCheck(this);
    }
    bool equalityCheck(const Base* rhs) const override {
        return false; 
    }
    bool equalityCheck(const A* rhs) const override{
        return a == rhs->a;
    }
};

override関数が実際にベースの仮想関数をオーバーライドすることを確認するために を使用することに注意してください。

この実装では、次の理由で機能します。

  • A::equalityBounce()電話しますBase::equalityCheck()
  • この関数のオーバーロードされたすべてのバージョンの中Base::equalityCheck(A*)からthisA*
  • 呼び出されたBase *lhsオブジェクトはその を呼び出しますequalityCheck(A*)。が である場合lhsは、期待される (正しい) 結果を生成するために使用されますA*おめでとう !A::equalityCheck(A*)
  • も から派生したlhs別のクラスへのポインタであるとします。この場合、を呼び出して、とを比較することを考慮して、正しい応答を返すこともできます。 XBaselhs->equalityCheck(A*)X::equalityCheck(A*)XA

拡張可能にする方法は?ダブル発送マップ!

厳密に型指定された言語を使用した二重ディスパッチの問題は、「バウンスされた」オブジェクトが特定の (事前にわかっている) クラスと比較する方法を知る必要があることです。ソース オブジェクトとバウンスされたオブジェクトは同じポリモーフィック ベース タイプであるため、ベースは関連するすべてのタイプを認識する必要があります。この設計により、拡張性が大幅に制限されます。

基本クラスで事前に知らなくても派生型を追加できるようにする場合は、動的型 (dynamic_cast または typeid) を使用する必要があります。

ここで動的な拡張性を提案します。同じタイプの 2 つのオブジェクトを比較するためにシングル ディスパッチを使用し、それらの間で異なるタイプを比較するためにダブル ディスパッチ マップを使用します (何も宣言されていない場合、デフォルトで false を返します)。

struct Base {
    typedef bool(*fcmp)(const Base*, const Base*);  // comparison function
    static unordered_map < type_index, unordered_map < type_index, fcmp>> tcmp;  // double dispatch map

    virtual bool operator==(const Base& rhs) const
    {
        if (typeid(*this) == typeid(rhs)) {  // if same type, 
            return equalityStrict(&rhs);     // use a signle dispatch
        }
        else {                              // else use dispatch map.  
            auto i = tcmp.find(typeid(*this));
            if (i == tcmp.end() ) 
                return false;              // if nothing specific was foreseen...
            else {
                auto j = i->second.find(typeid(rhs));
                return j == i->second.end() ? false : (j->second)(this, &rhs);
            }
        }
    }
    virtual bool equalityStrict(const Base* rhs) const = 0;  // for comparing two objects of the same type
};  

A クラスは次のように書き直されます。

struct A : public Base {
    A(int eh) : a(eh) {}
    int a;
    bool equalityStrict(const Base* rhs) const override {  // how to compare for the same type
        return (a == dynamic_cast<const A*>(rhs)->a); 
        }
};

このコードを使用すると、任意のオブジェクトを同じタイプのオブジェクトと比較できます。拡張性を示すためにstruct X、 と同じメンバーを持つを作成しましたA。A を X と比較できるようにするには、比較関数を定義するだけです。

bool iseq_X_A(const Base*x, const Base*a) {
    return (dynamic_cast<const X*>(x)->a == dynamic_cast<const A*>(a)->a);
}  // not a member function, but a friend.  

次に、動的二重ディスパッチを機能させるには、この関数を二重ディスパッチ マップに追加する必要があります。

Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A;

その後、結果は簡単に確認できます。

Base *w = new A(1), *x = new A(2), *y = new X(2);
std::cout << (*w == *w) << "\n";  // true returned by A::equalityStrict
std::cout << (*w == *x) << "\n";  // false returned by A::equalityStrict 
std::cout << (*y == *x) << "\n";  // true returned by isseq_X_A
于 2015-03-07T21:01:39.170 に答える
0

のために遅いdynamic_castですが、最も快適な解決策は次のものだと思います:

#include <iostream>

struct Base {
    virtual bool operator==(const Base& rhs) const
    { return equalityBounce(&rhs); }

    virtual bool equalityBounce(const Base* lhs) const = 0;
};

template<typename Derived>
struct BaseHelper : public Base
{
    bool equalityBounce(const Base* rhs) const
    {
        const Derived* p_rhs = dynamic_cast<const Derived*>(rhs);

        if (p_rhs == nullptr)
            return false;
        else
            return p_rhs->equalityCheck
             (reinterpeter_cast<const Derived*>(this));
    }

    virtual bool equalityCheck(const Derived*) const = 0;
};

struct A : public BaseHelper<A> {

    A(int eh) : a(eh) {}
    int a;

    virtual bool equalityCheck(const A* rhs) const
    { return a == rhs->a; }
};


int main() {

   Base *w = new A(1), *x = new A(2);

   std::cout << (*w == *w) << "\n"; // Prints 1.
   std::cout << (*w == *x) << "\n"; // Prints 0.
}

このように、「自分」と同じ派生型のオブジェクトを比較することだけに注意すればよいのです。BaseHelper がすべての補助的な作業を行うため、オーバーロードをさらに記述する必要はありません。

于 2015-03-07T22:55:10.807 に答える