Элегантное сравнение объектов

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

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

Нет, имеет смысл использовать оператор ==. Вот для чего это нужно. Функция сравнения не требуется.

jalf 16.11.2008 20:34

@jalf Я думаю, он хочет провести сравнение больше, чем равно или меньше, как это делает strcmp (). Обратите внимание, что Compare () возвращает int, а не bool.

Kyle Simek 09.05.2009 00:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
2
1 719
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

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

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

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++ сравнение должно быть виртуальным, и вы должны использовать проверку типов, чтобы увидеть, что параметр имеет «тот же» тип, что и текущий объект.

Сравнение, а не равенство. подумайте о strcmp ().

Kyle Simek 09.05.2009 01:03

В дополнение к 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;
    }
}

Обновлено: необходимо скомпилировать перед публикацией ...

предупреждение: в B :: Compare Other - это объект, поэтому ваш код пытается преобразовать объект в указатель. Более того, возвращение нуля означало бы равенство; Вместо этого я бы поднял какое-то исключение

Federico A. Ramponi 16.11.2008 19:19

вы бы подняли какое-то исключение в этом случае? Это ужасно.

coppro 16.11.2008 21:09

Совершенно разумно предположить, что вы можете сравнить A и B разных типов, унаследованных от одного и того же базового типа. Ваш пример, похоже, предполагает, что мы заботимся о сравнении только тогда, когда они одного типа. Кроме того, неясно, что вы собираетесь делать, когда они разных типов. Похоже, вы реализуете операцию равенства, а не сравнение, и вы возвращаете ноль, чтобы указать на неравенство, и, предположительно, 1, чтобы указать на равенство. Это отличается от того, что было задано в вопросе, который является операцией больше / меньше.

Kyle Simek 09.05.2009 00:41

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

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

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

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 - это A, а b - это B) может возвращать равенство и никогда генерирует исключение, тогда как b.Compare(a) будет. Иногда это то, чего вы хотите, а иногда нет. Если это не так, то вы, вероятно, не хотите, чтобы ваша функция Compare была виртуальной, или вы хотите сравнить type_info в базовой функции 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.

Проблема в том, что если часть B объекта отличается, но часть A такая же, она возвращает равенство.

coppro 16.11.2008 21:03

@coppro, я не согласен. Помните, что 0 означает равенство.

wimh 16.11.2008 22:25

Мне больше всего нравится это решение из всех предложенных, но оно предполагает, что значение a более значимо, чем значение b в сравнении, и что b имеет значение только тогда, когда оба a равны. Это может быть правдой, но это сильное предположение, и это не всегда так. К сожалению, я до сих пор думаю, что единственный ответ на этот вопрос - «это зависит от обстоятельств».

Kyle Simek 09.05.2009 01:01

Наверное, я бы так сделал:

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

По-прежнему существует проблема, что когда вы возвращаете «не равно», когда типы различаются, что вы возвращаете? Вы говорите «не равно», но ваш выбор для «не равно»: -1 для большего или 1 для меньшего. Ни то, ни другое в данном случае не имеет смысла. Кроме того, как и решение «P-daddy», здесь предполагается, что значение типа B не может преобладать над значением типа A. Рассмотрим сравнение «жестокости» животного, где тип A - «Животное», которое сравнивает только силу, а тип B - «Хищник». ", который (ради аргумента) всегда более жесток, чем другие животные, независимо от силы. Значение B имеет приоритет над A.

Kyle Simek 09.05.2009 01:10

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

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;
}
Ответ принят как подходящий

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

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

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 при сравнении возраста).

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

Короче говоря ... "смотря по обстоятельствам".

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