Pregunta

Al comparar dos objetos (del mismo tipo), tiene sentido tener una función de comparación que tome otra instancia de la misma clase. Si implemento esto como una función virtual en la clase base, entonces la firma de la función también debe hacer referencia a la clase base en las clases derivadas. ¿Cuál es la forma elegante de abordar esto? ¿La comparación no debe ser virtual?

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);
}
¿Fue útil?

Solución

Depende de la semántica prevista de A, B y C y de la semántica de compare (). La comparación es un concepto abstracto que no necesariamente tiene un solo significado correcto (o ningún significado en absoluto). No hay una única respuesta correcta a esta pregunta.

Aquí hay dos escenarios donde comparar significa dos cosas completamente diferentes con la misma jerarquía de clases:

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

Podemos considerar (al menos) dos formas de comparar dos cebras: ¿cuál es más antiguo y cuál tiene más volumen? Ambas comparaciones son válidas y fácilmente computables; la diferencia es que podemos usar el volumen para comparar una cebra con cualquier otro objeto, pero solo podemos usar la edad para comparar las cebras con otros animales. Si queremos que compare () implemente la semántica de comparación de edad, no tiene sentido definir compare () en la clase Object, ya que la semántica no está definida en este nivel de la jerarquía. Vale la pena señalar que ninguno de estos escenarios requiere ningún tipo de lanzamiento, ya que la semántica se define al nivel de la clase base (ya sea Objeto al comparar el volumen o Animal al comparar la edad).

Esto plantea el problema más importante: que algunas clases no son adecuadas para una sola función de comparación general (). A menudo tiene más sentido implementar múltiples funciones que expresan explícitamente lo que se está comparando, como compare_age () y compare_volume (). La definición de estas funciones puede ocurrir en el punto de la jerarquía de herencia donde la semántica se vuelve relevante, y debería ser trivial adaptarlas a las clases secundarias (si es necesario adaptarlas). La comparación simple usando compare () u operator == () a menudo solo tiene sentido con clases simples donde la implementación semántica correcta es obvia y sin ambigüedades.

Larga historia corta ... " depende " ;.

Otros consejos

Lo implementaría así:

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

Esto es muy similar al patrón IComparable en .NET, que funciona muy bien.

EDITAR:

Una advertencia a lo anterior es que a.Compare (b) (donde a es una A y b es una B) puede devolverá la igualdad y nunca arrojará una excepción, mientras que b.Compare (a) lo hará. A veces esto es lo que quieres, y a veces no. Si no es así, entonces probablemente no desee que su función Compare sea virtual, o desee comparar type_info s en la base Compare función, como en:

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

Tenga en cuenta que las funciones Compare de las clases derivadas no necesitan cambiar, ya que deben llamar a la Compare de la clase base, donde el type_info La comparación ocurrirá. Sin embargo, puede reemplazar el dynamic_cast en la función Compare anulada con un static_cast .

Probablemente, lo haría así:

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

Una comparación debe ser reflexiva, entonces:

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

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

Entonces a.equals (b) debería devolver falso, ya que B probablemente contiene campos que A no tiene, lo que significa que b.equals (a) probablemente ser falso.

Por lo tanto, en C ++, la comparación debería ser virtual, supongo, y debería usar la verificación de tipo para ver que el parámetro es del '' mismo '' escriba como el objeto actual.

Si quiere decir que Comparar () en la clase B o C siempre debe pasar un objeto de la clase B o C, no importa lo que diga la firma, puede trabajar con punteros a instancias en lugar de instancias, y tratar de bajar el puntero en el código del método usando algo como

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

(Tal sobrecarga sería necesaria solo para aquellas clases derivadas que agregan al estado de sus padres algo que influye en la comparación).

Apenas tengo este problema en C ++. A diferencia de Java, no estamos obligados a heredar todas nuestras clases de una misma clase de objeto raíz. Cuando se trata de clases comparables (/ semántica de valor), es muy poco probable que provengan de una jerarquía polimórfica.

Si la necesidad es real en su situación particular, está de vuelta en un problema de doble despacho / métodos múltiples. Hay varias formas de resolverlo (dynamic_cast, tablas de funciones para las posibles interacciones, visitantes, ...)

Además de dynamic_cast, también debe pasar una referencia o un puntero, probablemente const. La función Comparar también puede ser probablemente 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;
    }
}

EDITAR: debe compilarse antes de publicar ...

Sugeriría no hacerlo virtual. La única desventaja es que tienes que decir explícitamente qué comparar usar si las clases no son las mismas. Pero debido a que tiene que hacerlo, puede detectar un error (en tiempo de compilación) que de lo contrario podría causar un error de tiempo de ejecución ...

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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top