Swiss Frank
Swiss Frank

Reputation: 2422

how to test for equality between objects with common superclass

I have a virtual class called X and subclasses X1 X2 and X3.

Clarification: these classes are dumb data: int, double, string, vector<> of the preceding. There may be pointers to read-only static data structures whereby equality is a shallow check that the pointers be equal. There won't be for instance FILE* where it there are many ways you to decide an open file is equal.

I have two pointers of type X* that in fact point to subclasses.

What's the easiest way to check for equality?

Just to show I've put some work into it, my plan is: give X a virtual method IsEqual() that takes a X* pxThat. Each subclass implements this method. The method would use a dynamic_cast<> to see if the argument is its own class and if not report false. Otherwise it would used the dynamically-cast argument to check equality field by field.

Still, I'm wondering if there's an easier way to do it?

Upvotes: 0

Views: 222

Answers (1)

Eljay
Eljay

Reputation: 5321

In C++, often the virtual methods are public.

There is an idiom that some C++ programs use where the base class public methods are not virtual, and it is used to call a private virtual method. The rationale for this approach is that it separates out the public facing API from the derived class extensibility API (the virtual), rather than having the public facing API and the derived class API entangled and commingled. It is called the non-virtual interface idiom.

It appears to me that with your given problem, it is a good place to utilize the non-virtual interface idiom. Because it makes the non-virtual X::operator== implementation cleaner and the virtual isEqual cleaner and simpler, otherwise it could get pretty convoluted.

isEqual() is only called by operator==, and only when that operator has determined the class matches. Since this is guaranteed, the isEqual() implementation in the subclasses is best done with static_cast, which is slightly faster than dynamic_cast.

#include <typeinfo>
#include <cassert>

namespace {

struct X {
    virtual ~X() = default;
    bool operator==(X const&) const;
private:
    // Allow objects of superclass X to be compared, and supply a
    // working method for subclasses who have no additional fields to
    // check to establish equality.
    virtual bool isEqual(X const& rhs) const { return true; };
};

class X1 : public X {
    int a;
    bool isEqual(X const&) const override;
public:
    X1() : a{} {}
    X1(int a_) : a{a_} {}
};

struct X2 : X { };

struct X3 : X { };

bool X::operator==(X const& rhs) const {
    return typeid(*this) == typeid(rhs) && isEqual(rhs);
}

bool X1::isEqual(X const& rhs) const {
    // isEqual MUST only be called if lhs and rhs are the same type.
    auto const& r = static_cast<X1 const&>(rhs);
    return a == r.a;
}

} // namespace anon



int main() {
    X x;
    X1 x1{7};
    X2 x2;
    X3 x3;

    assert(x == X{});
    assert(x1 != X{});
    assert(x2 != X{});
    assert(x3 != X{});
    assert(x1 == X1{7});
    assert(x2 == X2{});
    assert(x3 == X3{});
    assert(x1 != X2{});
    assert(x2 != X3{});
    assert(x3 != X1{});
    assert(x1 != X1{6});
}

Upvotes: 1

Related Questions