9

(同じ型の) 2 つのオブジェクトを比較する場合、同じクラスの別のインスタンスを取る比較関数を使用することは理にかなっています。これを基本クラスの仮想関数として実装すると、関数のシグネチャは派生クラスの基本クラスも参照する必要があります。これに取り組むエレガントな方法は何ですか?Compare は仮想であってはなりませんか?

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);
}
4

8 に答える 8

1

これは、A、B、およびCの意図されたセマンティクスとcompare()のセマンティクスに依存します。比較は抽象的な概念であり、必ずしも単一の正しい意味(または、さらに言えば、まったく意味)を持っているとは限りません。この質問に対する正しい答えは1つではありません。

これが、比較が同じクラス階層を持つ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)、これらのシナリオのいずれもキャストを必要としないことに注意してください。

これにより、より重要な問題が発生します。一部のクラスは、単一のキャッチオールcompare()関数に適していないということです。多くの場合、compare_age()やcompare_volume()のように、比較対象を明示的に示す複数の関数を実装する方が理にかなっています。これらの関数の定義は、セマンティクスが関連するようになる継承階層のポイントで発生する可能性があり、子クラスに適応させるのは簡単なはずです(適応する必要がある場合)。compare()またはoperator ==()を使用した単純な比較は、多くの場合、正しいセマンティック実装が明白で明確な単純なクラスでのみ意味があります。

簡単に言えば...「状況によります」。

于 2009-05-08T17:43:06.200 に答える
1

私は次のように実装します:

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;
}

IComparableこれは、非常にうまく機能する .NETのパターンに非常に似ています。

編集:

上記の注意点の 1 つは、a.Compare(b)(ここでaは A でbあり、B です) は等値を返す可能性があり、例外をスローすることはありませんb.Compare(a)が、例外をスローすることです。これが必要な場合もあれば、そうでない場合もあります。そうでない場合は、おそらく関数を仮想にしたくないか、次のように基本関数でsCompareを比較したいでしょう。type_infoCompare

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基本クラスの を呼び出す必要があるため、変更する必要がないことに注意してください。ただし、オーバーライドされた関数の をに置き換えることはできます。Comparetype_infodynamic_castComparestatic_cast

于 2008-11-16T17:46:50.760 に答える
1

おそらく、私は次のようにします:

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 */
    }
  }
};
于 2008-11-16T18:11:12.617 に答える
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;
}
于 2008-11-16T19:20:02.010 に答える
0

クラス B または C の Compare() には常にクラス B または C のオブジェクトを渡す必要があるという意味であれば、シグネチャが何を言おうと、インスタンスの代わりにインスタンスへのポインターを操作し、ポインターをダウンキャストしてみることができます。次のようなものを使用したメソッドのコード

int B::Compare(A *ptr)
{
   other = dynamic_cast <B*> (ptr);
   if(other)
      ...  // Ok, it was a pointer to B
}

(このようなオーバーロードは、比較に影響を与える何かを親の状態に追加する派生クラスに対してのみ必要です。)

于 2008-11-16T15:53:48.120 に答える
0

比較は反映的でなければならないため、次のようになります。

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)B にはおそらく A にはないフィールドが含まれているため、False が返されるはずb.equals(a)です。

したがって、C++ では、比較は仮想である必要があり、型チェックを使用して、パラメーターが現在のオブジェクトと「同じ」型であることを確認する必要があります。

于 2008-11-16T16:06:23.880 に答える
0

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;
    }
}

編集:投稿する前にコンパイルする必要があります...

于 2008-11-16T16:09:55.170 に答える
0

C ++でこの問題はほとんどありません。Java とは異なり、同じルート オブジェクト クラスからすべてのクラスを継承する必要はありません。同等の (/value セマンティクス) クラスを扱う場合、それらがポリモーフィック階層に由来する可能性はほとんどありません。

特定の状況でその必要性が現実のものである場合は、二重ディスパッチ/マルチメソッドの問題に戻ります。それを解決するにはさまざまな方法があります (dynamic_cast、可能な相互作用のための関数のテーブル、訪問者など)

于 2008-11-16T16:43:31.047 に答える