Pergunta

Ao comparar dois objetos (do mesmo tipo), não faz sentido ter uma função de comparação que leva outra instância da mesma classe. Se eu implementar isso como uma função virtual na classe base, então a assinatura da função tem de fazer referência a classe base em classes derivadas também. Qual é a maneira elegante de lidar com isso? Caso o não Comparar 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);
}
Foi útil?

Solução

depende da semântica pretendidas de A, B, e C e a semântica de comparar (). Comparação é um conceito abstrato que não tem necessariamente um único significado correto (ou qualquer significado em tudo, para que o assunto). Não existe uma única resposta certa para esta questão.

Aqui está dois cenários onde comparam significa duas coisas completamente diferentes com a mesma hierarquia de classe:

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 (pelo menos) duas maneiras de comparar duas Zebras: o que é mais velho, e que tem mais volume? Ambas as comparações são válidos e facilmente calculável; a diferença é que podemos usar volume para comparar uma zebra com qualquer outro objeto, mas só podemos usar idade para comparar Zebras com outros animais. Se quisermos comparar () para implementar a semântica de comparação de idade, não faz qualquer sentido para definir compare () na classe de objeto, uma vez que a semântica não estão definidos neste nível de hierarquia. É importante notar que nenhum desses cenários exige qualquer casting, que seja, como a semântica são definidos no nível da classe base (seja objeto quando se compara o volume, ou animal quando se compara a idade).

Isto levanta a questão mais importante - que algumas classes não são adequados para a todas as capturas de uma única função compare (). Muitas vezes, faz mais sentido para implementar várias funções que afirmam explicitamente o que está sendo comparado, como compare_age () e compare_volume (). A definição destas funções podem ocorrer no ponto na hierarquia de herança, onde a semântica tornam-se relevantes, e deve ser trivial para adaptá-los às classes filho (se a necessidade se adaptar a todos). comparação simples usando compare () ou o operador == () muitas vezes só fazem sentido com classes simples onde a implementação semântica correta é óbvio e inequívoco.

Para encurtar a história ... "depende".

Outras dicas

Gostaria de implementá-lo como este:

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

Isto é muito semelhante ao padrão IComparable em .NET, que funciona muito bem.

EDIT:

Uma ressalva ao acima é que a.Compare(b) (onde a é um A e b é um B) pode retornar a igualdade, e não lançar uma exceção, enquanto b.Compare(a) vai. Às vezes, isso é o que você quer, e às vezes não é. Se não for, então você provavelmente não quer que sua função Compare ser virtual, ou você quiser comparar type_infos na função de base Compare, como em:

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

Note que as funções Compare das classes derivadas não precisam de mudança, uma vez que eles devem chamar Compare da classe base, onde a comparação type_info irá ocorrer. Você pode, no entanto, substituir o dynamic_cast na função Compare substituído com um static_cast.

Provavelmente, eu faria assim:

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

A comparação deve ser reflexivo, assim:

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

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

Assim, o a.equals(b) deve retornar falso, pois B provavelmente contém campos que um não tem que b.equals(a) meios provavelmente será falsa.

Assim, em C ++ a comparação deve ser Acho virtual, e você deve usar o tipo de verificação para ver que o parâmetro é do "mesmo" tipo do objeto atual.

Se você quer dizer que a Compare () na classe B ou C deve sempre ser passado um objeto da classe B ou C, não importa o que a assinatura diz, você pode trabalhar com ponteiros para as instâncias em vez de instâncias, e tentar abatido o ponteiro no código do 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 seria necessário apenas para aquelas classes derivadas que contribuem para o seu estado de algo pai que influencia a comparação.)

Eu quase não tem esse problema em C ++. Ao contrário de Java, não somos obrigados a herdar todas as nossas aulas a partir de uma mesma classe de objeto raiz. Ao lidar com (/ semântica de valor) classes comparáveis, é muito pouco provável que tê-los vir de uma hierarquia polimórfica.

Se a necessidade é real em sua situação particular, você está de volta para a / problema multimétodos double-expedição. Existem várias maneiras de resolvê-lo (dynamic_cast, tábuas de funções para as possíveis interações, visitantes, ...)

Além do dynamic_cast, você também precisa passar uma referência ou um ponteiro, provavelmente const. A função Compare também pode provavelmente ser 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: deve compilar antes de postar ...

gostaria de sugerir a não torná-lo virtual. A única desvantagem é que você explícita têm a dizer que comparar com o uso se as classes não são os mesmos. Mas porque você tem que, você pode detectar um erro (em tempo de compilação) que de outra forma poderia causar um erro de execução ...

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top