Domanda

Quando si confrontano due oggetti (dello stesso tipo), ha senso avere una funzione di confronto che accetta un'altra istanza della stessa classe. Se lo implemento come una funzione virtuale nella classe base, la firma della funzione deve fare riferimento anche alla classe base nelle classi derivate. Qual è il modo elegante per affrontare questo? Il confronto non dovrebbe essere virtuale?

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);
}
È stato utile?

Soluzione

Dipende dalla semantica prevista di A, B e C e dalla semantica di compare (). Il confronto è un concetto astratto che non ha necessariamente un singolo significato corretto (o nessun significato, per quella materia). Non esiste un'unica risposta corretta a questa domanda.

Ecco due scenari in cui compare significa due cose completamente diverse con la stessa gerarchia di classi:

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

Possiamo considerare (almeno) due modi di confrontare due zebre: quale è più vecchio e quale ha più volume? Entrambi i confronti sono validi e facilmente calcolabili; la differenza è che possiamo usare il volume per confrontare una zebra con qualsiasi altro oggetto, ma possiamo usare solo l'età per confrontare le zebre con altri animali. Se vogliamo compare () per implementare la semantica del confronto di età, non ha senso definire compare () nella classe Object, poiché la semantica non è definita a questo livello della gerarchia. Vale la pena notare che nessuno di questi scenari richiede alcun cast, in ogni caso, poiché la semantica è definita a livello della classe base (sia che si tratti di Oggetto quando si confronta il volume o Animale quando si confronta l'età).

Ciò solleva il problema più importante: che alcune classi non sono adatte a una singola funzione compare () catch-all. Spesso ha più senso implementare più funzioni che dichiarano esplicitamente ciò che viene confrontato, come compare_age () e compare_volume (). La definizione di queste funzioni può avvenire nel punto nella gerarchia dell'ereditarietà in cui la semantica diventa rilevante e dovrebbe essere banale adattarli alle classi secondarie (se necessario). Il semplice confronto con compare () o operator == () spesso ha senso solo con classi semplici in cui l'implementazione semantica corretta è ovvia e inequivocabile.

Per farla breve ... " dipende " ;.

Altri suggerimenti

Lo implementerei così:

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

Questo è molto simile al modello IComparable in .NET, che funziona molto bene.

EDIT:

Un avvertimento a quanto sopra è che a.Compare (b) (dove a è una A e b è una B) può restituirà l'uguaglianza e non non genererà mai un'eccezione, mentre b.Compare (a) lo farà. A volte questo è quello che vuoi, a volte no. In caso contrario, probabilmente non vuoi che la tua funzione Compare sia virtuale o vuoi confrontare type_info nella Compare di base funzione, come in:

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

Nota che le funzioni Compare delle classi derivate non devono cambiare, poiché dovrebbero chiamare il Compare della classe base, dove il type_info si verificherà il confronto. Tuttavia, puoi sostituire il dynamic_cast nella funzione Compare ignorata con un static_cast .

Probabilmente lo farei così:

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

Un confronto deve essere riflessivo, quindi:

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

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

Quindi a.equals (b) dovrebbe restituire false, poiché B probabilmente contiene campi che A non ha, il che significa che b.equals (a) probabilmente sii falso.

Quindi, in C ++ il confronto dovrebbe essere virtuale, suppongo, e dovresti usare il controllo del tipo per vedere che il parametro è dello stesso "quot" digitare come l'oggetto corrente.

Se intendi che Compare () in classe B o C dovrebbe sempre passare un oggetto di classe B o C, indipendentemente da ciò che dice la firma, puoi lavorare con i puntatori alle istanze anziché alle istanze e provare a eseguire il downcast il puntatore nel codice del metodo usando qualcosa come

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

(Un tale sovraccarico sarebbe necessario solo per quelle classi derivate che aggiungono allo stato del genitore qualcosa che influenza il confronto.)

Difficilmente ho questo problema in C ++. A differenza di Java, non ci viene richiesto di ereditare tutte le nostre classi da una stessa classe Object radice. Quando si hanno a che fare con classi comparabili (/ semantiche di valore), è molto improbabile che provengano da una gerarchia polimorfica.

Se la necessità è reale nella tua particolare situazione, sei di nuovo a un problema di doppia spedizione / multimetodi. Esistono vari modi per risolverlo (dynamic_cast, tabelle di funzioni per le possibili interazioni, visitatori, ...)

Oltre a dynamic_cast, devi anche passare un riferimento o un puntatore, probabilmente const. La funzione Confronta può anche essere 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;
    }
}

EDIT: compilare prima di pubblicare ...

Suggerirei di non renderlo virtuale. L'unico svantaggio è che devi esplicitamente dire quale confronto usare se le classi non sono uguali. Ma poiché è necessario, potresti individuare un errore (in fase di compilazione) che altrimenti potrebbe causare un errore di runtime ...

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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top