При сравнении двух объектов (одного типа) имеет смысл иметь функцию сравнения, которая принимает другой экземпляр того же класса. Если я реализую это как виртуальную функцию в базовом классе, тогда сигнатура функции должна также ссылаться на базовый класс в производных классах. Как лучше всего с этим справиться? Разве 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 Я думаю, он хочет провести сравнение больше, чем равно или меньше, как это делает strcmp (). Обратите внимание, что Compare () возвращает int, а не bool.





Если вы имеете в виду, что 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 ().
В дополнение к 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 - это объект, поэтому ваш код пытается преобразовать объект в указатель. Более того, возвращение нуля означало бы равенство; Вместо этого я бы поднял какое-то исключение
вы бы подняли какое-то исключение в этом случае? Это ужасно.
Совершенно разумно предположить, что вы можете сравнить A и B разных типов, унаследованных от одного и того же базового типа. Ваш пример, похоже, предполагает, что мы заботимся о сравнении только тогда, когда они одного типа. Кроме того, неясно, что вы собираетесь делать, когда они разных типов. Похоже, вы реализуете операцию равенства, а не сравнение, и вы возвращаете ноль, чтобы указать на неравенство, и, предположительно, 1, чтобы указать на равенство. Это отличается от того, что было задано в вопросе, который является операцией больше / меньше.
У меня почти не возникает этой проблемы в 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, я не согласен. Помните, что 0 означает равенство.
Мне больше всего нравится это решение из всех предложенных, но оно предполагает, что значение a более значимо, чем значение b в сравнении, и что b имеет значение только тогда, когда оба a равны. Это может быть правдой, но это сильное предположение, и это не всегда так. К сожалению, я до сих пор думаю, что единственный ответ на этот вопрос - «это зависит от обстоятельств».
Наверное, я бы так сделал:
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.
Я бы посоветовал не делать его виртуальным. Единственный недостаток заключается в том, что вы должны явно указать, какое сравнение использовать, если классы не совпадают. Но поскольку это необходимо, вы можете обнаружить ошибку (во время компиляции), которая в противном случае могла бы вызвать ошибку времени выполнения ...
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 == () часто имеет смысл только с простыми классами, где правильная семантическая реализация очевидна и однозначна.
Короче говоря ... "смотря по обстоятельствам".
Нет, имеет смысл использовать оператор ==. Вот для чего это нужно. Функция сравнения не требуется.