Question

Lors de la comparaison de deux objets (du même type), il est logique de disposer d’une fonction de comparaison prenant une autre instance de la même classe. Si j'implémente cela en tant que fonction virtuelle dans la classe de base, la signature de la fonction doit également référencer la classe de base dans les classes dérivées. Quel est le moyen élégant de s'y attaquer? La comparaison ne devrait-elle pas être virtuelle?

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);
}
Était-ce utile?

La solution

Cela dépend de la sémantique souhaitée pour A, B et C et de la sémantique de compare (). La comparaison est un concept abstrait qui n’a pas nécessairement une seule signification correcte (ni même une signification du tout). Il n’existe pas de bonne réponse à cette question.

Voici deux scénarios dans lesquels comparer signifie deux choses complètement différentes avec la même hiérarchie de classes:

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

Nous pouvons envisager (au moins) deux façons de comparer deux zèbres: le plus ancien et le plus volumineux? Les deux comparaisons sont valides et facilement calculables; La différence est que nous pouvons utiliser le volume pour comparer un zèbre avec n'importe quel autre objet, mais nous ne pouvons utiliser l'âge que pour comparer les zèbres avec d'autres animaux. Si nous voulons que compare () implémente la sémantique de la comparaison d'âge, cela n'a aucun sens de définir compare () dans la classe Object, car la sémantique n'est pas définie à ce niveau de la hiérarchie. Il est à noter que ni l'un ni l'autre de ces scénarios ne nécessite de transtypage, que ce soit, car la sémantique est définie au niveau de la classe de base (que ce soit Object lorsque l'on compare le volume ou Animal lorsque l'on compare l'âge).

Cela soulève le problème le plus important: certaines classes ne sont pas adaptées à une seule fonction fourre-tout à comparer (). Il est souvent plus logique d'implémenter plusieurs fonctions qui énoncent explicitement ce qui est comparé, comme compare_age () et compare_volume (). La définition de ces fonctions peut se produire au moment où la sémantique devient pertinente dans la hiérarchie d'héritage, et il devrait être trivial de les adapter aux classes enfant (si le besoin de le faire s'adapte). Une comparaison simple à l'aide de compare () ou de l'opérateur == () n'a souvent de sens qu'avec des classes simples dans lesquelles l'implémentation sémantique correcte est évidente et non ambiguë.

Longue histoire courte ... "Cela dépend".

Autres conseils

Je le mettrais en œuvre comme ceci:

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

Cela ressemble beaucoup au modèle IComparable dans .NET, qui fonctionne très bien.

EDIT:

Un inconvénient à ce qui précède est que a.Compare (b) (où a est un A et b est un B) peut renvoie une égalité, et jamais ne lève jamais une exception, alors que b.Compare (a) le fera. Parfois, c'est ce que vous voulez, et parfois non. Si ce n'est pas le cas, vous ne voulez probablement pas que votre fonction Compare soit virtuelle, ou vous voulez comparer des type_info dans la base Compare fonction, comme dans:

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

Notez que les fonctions Compare des classes dérivées n'ont pas besoin de changer, car elles doivent appeler le code Compare de la classe de base, où type_info la comparaison se produira. Vous pouvez toutefois remplacer le dynamic_cast dans la fonction Compare remplacée par un static_cast .

Probablement, je le ferais comme ceci:

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

Une comparaison doit être réfléchissante, donc:

let a = new A
let b = new B (inherits from A)

if (a.equals(b))
 then b.equals(a) must be true!

Ainsi, le a.equals (b) doit renvoyer false, car B contient probablement des champs que A n'a pas, ce qui signifie b.equals (a) être faux.

Ainsi, en C ++, la comparaison doit être virtuelle, je suppose, et vous devez utiliser la vérification de type pour vérifier que le paramètre est du même "même". tapez comme objet actuel.

Si vous voulez dire que le Compare () de classe B ou C doit toujours recevoir un objet de classe B ou C, quelle que soit la signature, vous pouvez utiliser des pointeurs sur des instances plutôt que sur des occurrences le pointeur dans le code de la méthode en utilisant quelque chose comme

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

(Une telle surcharge ne serait nécessaire que pour les classes dérivées qui ajoutent à l'état de leur parent quelque chose qui influence la comparaison.)

J'ai à peine ce problème en C ++. Contrairement à Java, il n'est pas nécessaire d'hériter de toutes nos classes d'une même classe d'objet racine. Lorsqu’il s’agit de classes comparables (/ sémantique de valeur), il est très peu probable qu’elles proviennent d’une hiérarchie polymorphe.

Si le besoin est réel dans votre situation particulière, vous vous retrouvez face à un problème de double expédition / méthodes multiples. Il existe différentes manières de le résoudre (dynamic_cast, tables de fonctions pour les interactions possibles, visiteurs, ...)

En plus de dynamic_cast, vous devez également transmettre une référence ou un pointeur, probablement const. La fonction de comparaison peut aussi probablement être constante.

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

EDIT: vous devez compiler avant de poster ...

Je suggérerais de ne pas le rendre virtuel. Le seul inconvénient est que vous devez indiquer explicitement quelle comparaison utiliser si les classes ne sont pas identiques. Mais comme vous devez le faire, vous pourriez repérer une erreur (au moment de la compilation) qui pourrait sinon provoquer une erreur d’exécution ...

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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top