2008-11-16 18 views
9

Al comparar dos objetos (del mismo tipo), tiene sentido tener una función de comparación que toma otra instancia de la misma clase. Si implemento esto como una función virtual en la clase base, 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?Comparación de objeto elegante

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); 
} 
+2

No, tiene sentido utilizar el operador ==. Para eso es para eso. No hay necesidad de una función de comparación. – jalf

+0

@jalf Creo que quiere hacer una comparación mayor, igual o menor que la de strcmp(). Observe que Compare() devuelve un int, no un bool. –

Respuesta

1

Depende de la semántica previstos de A, B, y C y la semántica de comparar(). La comparación es un concepto abstracto que no necesariamente tiene un único significado correcto (o ningún significado, para el caso). No hay una sola respuesta correcta para esta pregunta.

He aquí dos escenarios en los que se comparan 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 mayor, y que 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 edades, 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 conversión, ya que la semántica se define en el 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 única función de comparación catch-all(). A menudo tiene más sentido implementar múltiples funciones que indiquen explícitamente qué se está comparando, como compare_age() y compare_volume(). La definición de estas funciones puede ocurrir en el punto en la jerarquía de herencia donde la semántica se vuelve relevante, y debe ser trivial adaptarlas a las clases secundarias (si es necesario adaptarlas). La comparación simple usando compare() u operador ==() a menudo solo tiene sentido con clases simples donde la implementación semántica correcta es obvia e inequívoca.

Larga historia corta ... "depende".

0

Si se refiere a que la compare() en la clase B o C siempre se debe pasar un objeto de clase B o C, no importa lo que dice la firma, se puede trabajar con punteros a instancias, en vez de los casos, y tratar de abatido el puntero en código usando algo del método como

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

(una sobrecarga Tal sería necesaria sólo para aquellas clases derivadas que se suman a la situación de su padre algo que influye en la comparación.)

0

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! 

Así que la a.equals(b) debe devolver false, ya que B probablemente contiene campos que A no tiene que significa b.equals(a) será probablemente falsa.

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

+0

Comparación, no igual. piensa en strcmp(). –

0

Además del dynamic_cast, también necesita pasar una referencia o un puntero, probablemente const. La función de comparación también puede ser probablemente 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: debe establecer antes de publicar ...

+0

advertencia: en B :: Comparar, Otro es un objeto, por lo tanto, su código intenta convertir un objeto en un puntero. Además, regresar cero significaría igualdad; Me gustaría plantear alguna excepción en su lugar –

+0

que podría plantear alguna excepción en este caso? Eso es horrible. – coppro

+0

Gracias. El código debería compilarse ahora. –

0

casi no tienen 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 valores), es muy poco probable que provengan de una jerarquía polimórfica.

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

1

que implementaría esta manera:

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.

EDIT:

Una advertencia de lo anterior es que a.Compare(b) (donde a es una A y una B b es) puede devolver la igualdad, y no lanzar una excepción, mientras que b.Compare(a) voluntad. Algunas veces esto es lo que quieres, y otras no.Si no es así, entonces es probable que no quiere que su función Compare a ser virtual, o desea comparar type_info s en la función de base Compare, 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 clases derivadas Compare funciones don' Necesito cambiar, ya que deben llamar al Compare de la clase base, donde se realizará la comparación type_info. Sin embargo, puede reemplazar el dynamic_cast en la función Compare reemplazada con un static_cast.

+0

El problema es que si la parte B del objeto es diferente, pero la parte A es la misma, devuelve la igualdad. – coppro

+0

¿Cómo te imaginas? –

+0

@coppro, no estoy de acuerdo. Recuerde que 0 significa igualdad. – wimh

1

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

Todavía hay un problema que cuando devuelve "no igual" cuando los tipos son diferentes, ¿qué devuelve? Usted dice "no igual", pero sus elecciones para "no igual" son -1 para mayor que o 1 para menor que. Ni tiene sentido en este caso. Además, como la solución de P-daddy, esto supone que el valor del tipo B no puede anular el tipo A. Considere una comparación de la "ferocidad" de un animal, donde el tipo A es "Animal" que compara la fuerza solo, y el tipo B es "Depredador" ", que (por el bien de la discusión) es siempre más feroz que otros animales, independientemente de la fuerza. El valor de B anula A. –

0

sugeriría a no hacerlo virtual. La única desventaja es que tienes que decir explícitamente qué comparar para usar si las clases no son las mismas. Pero debido a que es necesario, se podrá encontrar un error (en tiempo de compilación) que de otro modo podría causar un error 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; 
} 
Cuestiones relacionadas