Проверка равенства для производных классов в С++
Возможный дубликат:
Какой правильный способ перегрузить operator == для иерархии классов?
В С++, как производные классы могут переопределить тест равенства базового класса значимым образом?
Например, скажем, что у меня есть базовый класс A. Классы B и C происходят от A. Теперь, учитывая два указателя на два объекта A, могу ли я проверить, являются ли они равными (включая любые данные подкласса)?
class A {
public: int data;
};
class B : public A {
public: float more_data; bool something_else;
};
class C : public A {
public: double more_data;
};
A* one = new B;
A* two = new B;
A* three = new C;
//How can I test if one, two, or three are equal
//including any derived class data?
Есть ли чистый способ сделать это? Какой у меня лучший выбор?
Спасибо!
Ответы
Ответ 1
Я помню, как я читал краткое описание публично-не виртуальной/непубличной виртуальной идиомы и ее преимуществ, но не там. Этот wikibook имеет хорошее описание.
Вот как вы примените его к op ==:
struct A {
virtual ~A() {}
int a;
friend
bool operator==(A const& lhs, A const& rhs) {
return lhs.equal_to(rhs);
}
// http://en.wikipedia.org/wiki/Barton-Nackman_trick
// used in a simplified form here
protected:
virtual bool equal_to(A const& other) const {
return a == other.a;
}
};
struct B : A {
int b;
protected:
virtual bool equal_to(A const& other) const {
if (B const* p = dynamic_cast<B const*>(&other)) {
return A::equal_to(other) && b == p->b;
}
else {
return false;
}
}
};
struct C : A {
int c;
protected:
virtual bool equal_to(A const& other) const {
if (C const* p = dynamic_cast<C const*>(&other)) {
return A::equal_to(other) && c == p->c;
}
else {
return false;
}
}
};
Ответ 2
Могут ли разные производные классы создавать одинаковые объекты?
Если да: двойная отправка - это вариант: ему нужно перегружать базовый класс, поэтому у вас будут зависимости
Если нет: решение находится в операторе ==(), чтобы проверить typeid и вернуть false, если они разные. В противном случае вызовите функцию private equal(), в которой производный класс может выполнять static_cast и сравнивать.
bool base::operator==(const base& other) const
{
if (typeid(*this) != typeid(other)) return false;
return equal(other);
}
bool derived::equal(const base& other) const
{
derived& derOther = static_cast<derived&>(other);
// compare derOther with *this
return true; // there is nothing to compare
}
Это позволяет избежать проверок типов во всех производных классах
Ответ 3
Один из способов сделать это - использовать virtual operator==
, который берет объект базового класса как параметр, чтобы он работал правильно с различными производными объектами. Однако вам нужно сделать эту функцию чистой виртуальной, чтобы заставить все производные объекты реализовать ее. Таким образом, вы не сможете создать базовый класс. Например:
class A
{
public:
virtual ~A(){}
//A virtual operator for comparison
virtual bool operator==(const A& a) = 0;
protected:
bool compareBase(const A& a);
private:
int m_data;
};
bool A::compareBase(const A &a)
{
return m_data == a.m_data;
}
class B1 : public A
{
public:
//Override the base class
bool operator==(const A& a);
private:
bool compare(const B1* pB)
{
if(compareBase(*pB))
{
//Code for compare
return true;
}
return false;
}
};
bool B1::operator ==(const A &a)
{
//Make sure that the passed type is same
const B1* pB = dynamic_cast<const B1*>(&a);
if(pB )
{
return compare(pB);
}
return false;
}
//Similarly implement for B2
Ответ 4
Если вам не нужны сравнения типа A с типом B, или от B до C и т.д., вы можете просто реализовать перегруженный оператор равенства для каждого класса:
class A {
public: int data;
bool operator==(const A& rhs) {
return (data == rhs.data);
}
};
class B : public A {
public: float more_data; bool something_else;
bool operator==(const B& rhs) {
return (A::operator==( data ) &&
more_data == rhs.more_data &&
something_else == rhs.something_else);
}
};
Это опасно, потому что, если вы получите новый класс D из B или C, у вас будут проблемы.
В противном случае вам нужно реализовать некоторые компараторы с большим количеством dynamic_cast < > , чтобы действительно сделать это правильно. В качестве альтернативы вы можете реализовать функцию для создания хэш-кода для каждого объекта и использовать его, например,
class A {
public: int data;
virtual long getHashCode() const {
// compute something here for object type A
}
// virtual here just in case you need to overload it in B or C
virtual bool equals( const A& obj ) const {
return (typeid(*this) == typeid(obj) &&
getHashCode() == obj->getHashCode());
}
};
class B : public A {
public: float more_data; bool something_else;
virtual long getHashCode() const {
// compute something here for object type B
}
};
class C : public A {
public: double more_data;
virtual long getHashCode() const {
// compute something here for object type C
}
};
Если вы включаете тип объекта в хеш-код каким-то образом (не показано выше), вы также можете отказаться от глупых сравнений typeid() выше.
Ответ 5
Если вы не возражаете против базового класса, относящегося к подклассам, затем дважды отправляйте:
#include <iostream>
class B;
class C;
class A
{
public:
int data;
virtual bool equals (const A* rhs) const
{
std::cout << " A==A ";
return data == rhs->data;
}
virtual bool equals (const B* rhs) const {return false;}
virtual bool equals (const C* rhs) const {return false;}
};
class B : public A
{
public:
float some_data;
virtual bool equals (const A* rhs) const
{
return rhs->equals (this);
}
virtual bool equals (const B* rhs) const
{
std::cout << " B==B ";
return A::equals (static_cast<const A*> (rhs)) && some_data == rhs->some_data;
}
};
class C : public A
{
public:
double more_data;
virtual bool equals (const A* rhs) const
{
return rhs->equals (this);
}
virtual bool equals (const C* rhs) const
{
std::cout << " C==C ";
return A::equals (static_cast<const A*> (rhs)) && more_data == rhs->more_data;
}
};
bool operator== (const A& lhs, const A& rhs)
{
return lhs.equals (&rhs);
}
int main (int argc, char* argv[])
{
A* one = new B;
A* two = new B;
A* three = new C;
std::cout << (*one == *one) << std::endl;
std::cout << (*one == *two) << std::endl;
std::cout << (*one == *three) << std::endl;
std::cout << (*three == *three) << std::endl;
return 0;
}
Это не требует динамических_casts.