c++ - online - Elegante comparación de objetos
virtual method c# (8)
Además de 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;
}
}
EDITAR: debe compilar antes de publicar ...
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, 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);
}
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 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, ...)
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 un A y b
es un B) puede devolver igualdad, y nunca arrojará una excepción, mientras que b.Compare(a)
hará. Algunas veces esto es lo que quieres, y otras no. Si no es así, es probable que no desee que su función de Compare
sea virtual, o si desea comparar type_info
s en la función 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 funciones de Compare
las clases derivadas no necesitan cambiar, ya que deben llamar a la Compare
la clase base, donde type_info
comparación type_info
. Sin embargo, puede reemplazar dynamic_cast
en la función Compare
con un static_cast
.
Si quiere decir que el Compare () 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 e intentar 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 su padre algo que influye en la comparación).
Sugeriría que no sea virtual. La única desventaja es que tienes que decir explícitamente qué comparar para usar si las clases no son las mismas. Pero como es necesario, es posible que detecte 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;
}
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 false, ya que B probablemente contiene campos que A no tiene, lo que significa que b.equals(a)
probablemente sea falso.
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.
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 único significado correcto (o ningún significado, para el caso). No hay una sola respuesta correcta para 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 antigua 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 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 sola función catch (all catch all) de comparación (). 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".
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 */
}
}
};