Here is a very simple class hierarchy:
class A
{
public:
A( int _a ) : a( _a ) {}
virtual bool operator==( const A& right ) const
{
return a == right.a;
}
virtual bool operator!=( const A& right ) const
{
return !( *this == right );
}
int a;
};
class B : public A
{
public:
B( int _a, int _b ) : A( _a ), b( _b ) {}
virtual bool operator==( const B& right ) const
{
return A::operator==( right ) && b == right.b;
}
int b;
};
As you can see, operator != is defined in the base class. Because I'm very lazy, I don't want to duplicate such a simple code in all the derived classes.
Unfortunatley, with this code:
A a4(4), a5(5), a4bis(4);
assert( a4 == a4bis );
assert( a4 != a5 );
B b1(4,5), b2(4,6);
assert( !(b1 == b2) );
assert( b1 != b2 ); // fails because B::operator== is not called!
b1 != b2
returns false, because it executes A::operator!=
which calls then A::operator==
rather than B::operator==
(even if the operator is virtual, as derived class version parameter is different, they are not linked in the vtable).
So what's the best way to adress != operator in a generic way for a class hierarchy?
One solution is to repeat it in each class, B
would then have:
virtual bool operator!=( const B& right ) const
{
return !( *this == right );
}
But that's a pain when you have many classes....I have 30....
Another solution would be to have a generic template approach:
template <class T>
bool operator!=( const T& left, const T& right )
{
return !( left == right );
}
But this bypasses any !=
operator defined by any class....so it may be risky if one declared it differently (or if one declared a ==
itself calling !=
, it would end up with an infinite loop...). So I feel this solution being very unsafe....except if we can limit the template to be used for all classes derived from the top-level class of our hierarchy (A
in my example)....but I don't think this is doable at all.
Note: I'm not using C++11 yet...sorry about that.
Your function in B
...
virtual bool operator==( const B& right ) const
...does not override the function in A
...
virtual bool operator==( const A& right ) const
...because the argument types differ. (Differences are only allowed for covariant return types.)
If you correct this, you'll then be able to choose how to compare B
objects to other A
and A
-derived objects, e.g. perhaps:
bool operator==( const A& right ) const override
{
if (A::operator==( right ))
if (typeid(*this) == typeid(right))
return b == static_cast<const B&>(right).b;
return false;
}
Note that using the typeid
comparison above means only B
objects will compare equal: any B
will compare unequal to any B
-derived object. This may or may not be what you want.
With an implementation for B::operator==
, the existing !=
implementation will properly wrap operator==
. As Jarod42 observes, your A::operator==
isn't robust in that when the left-hand-side value is an A
only the A
slice of the right-hand-side object will be compared... you might prefer:
virtual bool operator==(const A& right) const
{
return a == right.a && typeid(*this) == typeid(right);
}
This has the same issues as the B::operator==
above: e.g. an A
would compare un-equal to a derived object that didn't introduce further data members.
How about something like this ?
class A {
protected :
virtual bool equals(const A& right) const {
return (a == right.a);
}
public :
A(int _a) : a(_a) { }
bool operator==(const A& right) const {
return this->equals(right);
}
bool operator!=(const A& right) const {
return !(this->equals(right));
}
int a;
};
class B : public A {
protected :
virtual bool equals(const A& right) const {
if (const B* bp = dynamic_cast<const B*>(&right)) {
return A::equals(right) && (b == bp->b);
}
return false;
}
public :
B(int _a, int _b) : A(_a), b(_b) { }
int b;
};
Move the comparison logic to a separate (virtual) function equals
, and call that function from the operator==
and operator!=
defined in the base class.
The operators don't need to be re-defined in the derived classes. To change the comparison in a derived class, simply override equals
.
Note that the dynamic_cast
in the code above is used to ensure that the runtime type is a valid type for performing the comparison. Ie. for B::equals
, it's used to ensure that right
is a B
- this is necessary because otherwise right
would not have a b
member.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With