質問
(同じ型の)2つのオブジェクトを比較する場合、同じクラスの別のインスタンスを取得する比較関数を使用するのが理にかなっています。これを基本クラスの仮想関数として実装する場合、関数のシグネチャは派生クラスの基本クラスも参照する必要があります。これに取り組むエレガントな方法は何ですか?比較を仮想化すべきではありませんか?
class A
{
A();
~A();
virtual int Compare(A Other);
}
class B: A
{
B();
~B();
int Compare(A Other);
}
class C: A
{
C();
~C();
int Compare(A Other);
}
解決
A、B、およびCの意図したセマンティクスとcompare()のセマンティクスに依存します。比較とは抽象的な概念であり、必ずしも単一の正しい意味(またはその意味での意味)を持っているわけではありません。この質問に対する単一の正しい答えはありません。
比較は、同じクラス階層を持つ2つの完全に異なることを意味する2つのシナリオです。
class Object
{
virtual int compare(const Object& ) = 0;
float volume;
};
class Animal : Object
{
virtual int compare(const Object& );
float age;
};
class Zebra : Animal
{
int compare(const Object& );
};
2つのシマウマを比較する(少なくとも)2つの方法を検討できます。どちらがより古く、どちらがよりボリュームがありますか?両方の比較は有効であり、簡単に計算できます。違いは、ボリュームを使用してシマウマを他のオブジェクトと比較できることですが、年齢を使用してシマウマを他の動物と比較することしかできません。 compare()に年齢比較セマンティクスを実装する場合、Objectクラスでcompare()を定義することは意味がありません。セマンティクスは階層のこのレベルでは定義されないためです。セマンティクスは基本クラスのレベルで定義されているため、これらのシナリオはいずれもキャストを必要としないことに注意してください(ボリュームを比較する場合はObject、年齢を比較する場合はAnimal)。
これにより、より重要な問題が発生します。一部のクラスは、単一のcatch-all compare()関数に適していないということです。 compare_age()やcompare_volume()のように、比較対象を明示的に示す複数の関数を実装する方が理にかなっていることがよくあります。これらの関数の定義は、セマンティクスが関連する継承階層のポイントで発生する可能性があり、子クラスに適合させるのは簡単なはずです(適合が必要な場合)。 compare()またはoperator ==()を使用した単純な比較は、正しいセマンティック実装が明白で明確な単純なクラスでのみ意味をなすことがよくあります。
長編短文...「それは依存します」。
他のヒント
このように実装します:
class A{
int a;
public:
virtual int Compare(A *other);
};
class B : A{
int b;
public:
/*override*/ int Compare(A *other);
};
int A::Compare(A *other){
if(!other)
return 1; /* let's just say that non-null > null */
if(a > other->a)
return 1;
if(a < other->a)
return -1;
return 0;
}
int B::Compare(A *other){
int cmp = A::Compare(other);
if(cmp)
return cmp;
B *b_other = dynamic_cast<B*>(other);
if(!b_other)
throw "Must be a B object";
if(b > b_other->b)
return 1;
if(b < b_other->b)
return -1;
return 0;
}
これは、.NETの IComparable
パターンと非常によく似ており、非常によく機能します。
編集:
上記の注意点の1つは、 a.Compare(b)
( a
はAで、 b
はB)です。平等を返し、例外をスローすることはありませんが、 b.Compare(a)
は例外をスローします。時々これはあなたが望むものであり、時にはそうではありません。そうでない場合は、おそらく Compare
関数を仮想にしたくないか、ベース Compare
の type_info
を比較したいでしょう。次のような関数:
int A::Compare(A *other){
if(!other)
return 1; /* let's just say that non-null > null */
if(typeid(this) != typeid(other))
throw "Must be the same type";
if(a > other->a)
return 1;
if(a < other->a)
return -1;
return 0;
}
派生クラスの Compare
関数は、基本クラスの Compare
を呼び出す必要があるため、変更する必要がないことに注意してください。 type_info
比較が行われます。ただし、オーバーライドされた Compare
関数の dynamic_cast
を static_cast
に置き換えることができます。
おそらく、私はこのようにします:
class A
{
public:
virtual int Compare (const A& rhs) const
{
// do some comparisons
}
};
class B
{
public:
virtual int Compare (const A& rhs) const
{
try
{
B& b = dynamic_cast<A&>(rhs)
if (A::Compare(b) == /* equal */)
{
// do some comparisons
}
else
return /* not equal */;
}
catch (std::bad_cast&)
{
return /* non-equal */
}
}
};
比較は反射的でなければならないので:
let a = new A
let b = new B (inherits from A)
if (a.equals(b))
then b.equals(a) must be true!
したがって、 a.equals(b)
はfalseを返す必要があります。BにはおそらくAにないフィールドが含まれているため、 b.equals(a)
はおそらく偽である。
したがって、C ++では比較は仮想である必要があり、型チェックを使用してパラメーターが「同じ」であることを確認する必要があります。現在のオブジェクトとして入力します。
クラスBまたはCのCompare()に常にクラスBまたはCのオブジェクトを渡す必要があることを意味する場合、署名の内容に関係なく、インスタンスではなくインスタンスへのポインターを操作して、ダウンキャストを試みることができます次のようなものを使用して、メソッドのコード内のポインター
int B::Compare(A *ptr)
{
other = dynamic_cast <B*> (ptr);
if(other)
... // Ok, it was a pointer to B
}
(このようなオーバーロードは、比較に影響する何かを親の状態に追加する派生クラスにのみ必要です。)
C ++ではこの問題はほとんどありません。 Javaとは異なり、すべてのクラスを同じルートObjectクラスから継承する必要はありません。比較可能な(/ valueセマンティクス)クラスを扱う場合、それらが多態的な階層に由来することはほとんどありません。
特定の状況で必要性が現実的である場合、二重ディスパッチ/マルチメソッドの問題に戻ります。それを解決するさまざまな方法があります(dynamic_cast、可能な相互作用のための関数のテーブル、訪問者、...)
dynamic_castに加えて、参照またはポインター(おそらくconst)も渡す必要があります。 Compare関数もおそらくconstです。
class B: public A
{
B();
virtual ~B();
virtual int Compare(const A &Other) const;
};
int B::Compare(const A &Other) const
{
const B *other = dynamic_cast <const B*> (&Other);
if(other) {
// compare
}
else {
return 0;
}
}
編集:投稿する前にコンパイルする必要があります...
仮想化しないことをお勧めします。唯一の欠点は、クラスが同じでない場合に使用する比較を明示的に指定する必要があることです。しかし、そうする必要があるため、ランタイムエラーを引き起こす可能性のあるエラーを(コンパイル時に)発見する可能性があります...
class A
{
public:
A(){};
int Compare(A const & Other) {cout << "A::Compare()" << endl; return 0;};
};
class B: public A
{
public:
B(){};
int Compare(B const & Other) {cout << "B::Compare()" << endl; return 0;};
};
class C: public A
{
public:
C(){};
int Compare(C const & Other) {cout << "C::Compare()" << endl; return 0;};
};
int main(int argc, char* argv[])
{
A a1;
B b1, b2;
C c1;
a1.Compare(b1); // A::Compare()
b1.A::Compare(a1); // A::Compare()
b1.Compare(b2); // B::Compare()
c1.A::Compare(b1); // A::Compare()
return 0;
}