Вопрос

При сравнении двух объектов (одного и того же типа) имеет смысл иметь функцию сравнения, которая принимает другой экземпляр того же класса.Если я реализую это как виртуальную функцию в базовом классе, то сигнатура функции также должна ссылаться на базовый класс в производных классах.Каков элегантный способ решить эту проблему?Разве сравнение не должно быть виртуальным?

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);
}
Это было полезно?

Решение

Это зависит от предполагаемой семантики A, B и C и семантики сравнения (). Сравнение - это абстрактное понятие, которое не обязательно имеет единственное правильное значение (или вообще какое-либо значение). На этот вопрос нет единственно правильного ответа.

Вот два сценария, где сравнение означает две совершенно разные вещи с одинаковой иерархией классов:

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

Мы можем рассмотреть (как минимум) два способа сравнения двух зебр: какая старше, а какая больше? Оба сравнения корректны и легко вычислимы; Разница в том, что мы можем использовать объем, чтобы сравнить Зебру с любым другим Объектом, но мы можем использовать только возраст, чтобы сравнить Зебр с другими Животными. Если мы хотим, чтобы compare () реализовала семантику сравнения возраста, не имеет смысла определять compare () в классе Object, так как семантика не определена на этом уровне иерархии. Стоит отметить, что ни один из этих сценариев не требует какого-либо приведения, так как семантика определяется на уровне базового класса (будь то Object при сравнении объема или Animal при сравнении возраста).

Это поднимает более важную проблему - некоторые классы не подходят для одной функции catch (all) all compare (). Часто имеет смысл реализовывать несколько функций, которые явно указывают на то, что сравнивается, например, Compare_age () и Compare_volume (). Определение этих функций может происходить в той точке иерархии наследования, где семантика становится актуальной, и адаптировать их к дочерним классам должно быть тривиально (если вообще требуется адаптация). Простое сравнение с использованием Compare () или оператора == () часто имеет смысл только с простыми классами, где правильная семантическая реализация очевидна и однозначна.

Короче говоря ... "это зависит".

Другие советы

Я бы реализовал это так:

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

Это очень похоже на IComparable шаблон в .NET, который работает очень хорошо.

РЕДАКТИРОВАТЬ:

Одно предостережение к вышеизложенному заключается в том, что a.Compare(b) (где a это А и b является B) может возвращать равенство и будет никогда выдать исключение, тогда как b.Compare(a) воля.Иногда это то, чего вы хотите, а иногда нет.Если это не так, то вы, вероятно, не хотите, чтобы ваш Compare функция должна быть виртуальной, или вы хотите сравнить type_infos в базе Compare функция, например:

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

Обратите внимание, что производные классы Compare функции не нужно менять, поскольку они должны вызывать функции базового класса. Compare, где type_info произойдет сравнение.Однако вы можете заменить dynamic_cast в переопределенном Compare функция с static_cast.

Вероятно, я бы сделал это так:

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

Сравнение должно быть отражающим, поэтому:

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

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

Таким образом, a.equals (b) должен возвращать false, поскольку B, вероятно, содержит поля, которых нет в A, что означает, что b.equals (a) , вероятно, быть ложным.

Таким образом, в C ++ сравнение должно быть виртуальным, я полагаю, и вы должны использовать проверку типов, чтобы убедиться, что параметр имеет «тот же» статус. введите в качестве текущего объекта.

Если вы имеете в виду, что Compare () в классе B или C всегда должен передаваться объект класса B или C, независимо от того, что говорит подпись, вы можете работать с указателями на экземпляры вместо экземпляров и пытаться уменьшить указатель в коде метода, использующий что-то вроде

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

(Такая перегрузка была бы необходима только для тех производных классов, которые добавляют к состоянию своего родителя что-то, что влияет на сравнение.)

У меня вряд ли есть эта проблема в C ++. В отличие от Java, мы не обязаны наследовать все наши классы от одного и того же корневого класса Object. При работе с сопоставимыми (/ value семантикой) классами очень маловероятно, чтобы они происходили из полиморфной иерархии.

Если необходимость в вашей конкретной ситуации реальна, вы снова столкнулись с проблемой двойной отправки / мультиметода. Существуют различные способы ее решения (dynamic_cast, таблицы функций для возможных взаимодействий, посетители, ...)

В дополнение к dynamic_cast вам также необходимо передать ссылку или указатель, возможно, 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;
    }
}

РЕДАКТИРОВАТЬ: необходимо скомпилировать перед публикацией ...

Я бы предложил не делать это виртуальным. Единственным недостатком является то, что вы явно должны сказать, какое сравнение использовать, если классы не совпадают. Но поскольку вам нужно, вы можете обнаружить ошибку (во время компиляции), которая в противном случае могла бы вызвать ошибку во время выполнения ...

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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top