13

Possible Duplicate:
What’s the right way to overload operator== for a class hierarchy?

In C++, how can derived classes override the base class equality test in a meaningful way?

For example, say I have a base class A. Classes B and C derive from A. Now given two pointers to two A objects, can I test if they are equal (including any subclass data)?

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?

Is there a clean way of doing it? What's my best bet?

Thanks!

Community
  • 1
  • 1
Imbue
  • 3,897
  • 6
  • 40
  • 42
  • 2
    Possible duplicate: http://stackoverflow.com/questions/1691007/whats-the-right-way-to-overload-operator-for-a-class-hierarchy – CB Bailey Nov 19 '09 at 17:48
  • Do you want to compare `T==T`, where `T` might be either `A`, `B`, or `C` (fine), or do you want to compare `A` with `B` and `A` with `C` and `B` with `C` (questionable)? – sbi Nov 19 '09 at 17:50
  • In the example above, I want to compare one with two and three. They are all A pointers. – Imbue Nov 19 '09 at 18:23
  • @Imbue: Then what you want makes no sense. Since only a `B` has `something_else` _it can never be equal_ to an `A` or a `C`. What would be the semantics of such a comparison? – sbi Nov 20 '09 at 09:31
  • @sbi: I'm not asking to compare B to A, or B to C. I'm asking to compare two As together. In my example above, one, two, and three are all A*. – Imbue Nov 20 '09 at 20:37

5 Answers5

15

I remember reading a succinct description of the public-non-virtual/non-public-virtual idiom and its advantages, but not where. This wikibook has an okay description.

Here is how you apply it to 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;
    }
  }
};
Andy Dennie
  • 6,012
  • 2
  • 32
  • 51
  • Inside `C::equal_to()`, Is it better to replace `return A::equal_to(other) && c == p->c;` as `return B::equal_to(other) && c == p->c;`? This ensures that member `B::b` will be compared, and `B::equal_to(other)` will call `A::equal_to(other)` which then compares `A::a` (my point: each derived class compares its own subobject, and invokes its direct base class which automatically compares everything up the inheritance chain). – Peng Oct 03 '22 at 16:55
2

Can different derived classes make equal objects?

If so: double dispatch is an option: it does need overloading in the base class, so you will have dependencies

If not: a solution is in the operator==() to check the typeid and return false if they're different. Otherwise call a private equal() function in which the derived class can do a static_cast and compare.

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
}

This avoids type-checks in all derived classes

stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • This prevents using a derived class of B (say, B2) from being compared to B. (Using the hierarchy from the question.) –  Nov 19 '09 at 17:56
  • operator==() is only defined in base-class, so the hierarchy can be used. The equal() function must be private (as mentioned) and only called by operator==() – stefaanv Nov 19 '09 at 19:33
1

One way of doing this is to use the virtual operator== which takes the base class object as the parameter so that it works properly with different derived objects. However, you need to make this function pure virtual so as to force all the derived objects to implement it. So you will not be able instantiate the base class. For example:

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
Naveen
  • 74,600
  • 47
  • 176
  • 233
  • Having a non-pure public virtual operator== in a base class is potentially very dangerous as you have no protection or warnings for new derived classes that forget to override it and end up all comparing equal if just the base parts are equal. – CB Bailey Nov 19 '09 at 17:57
  • Yes, you are right. Edited the code so as to make it pure virtual – Naveen Nov 19 '09 at 18:04
  • While I agree that `A` should be abstract, I don't think that it needs (or should have) and operator== at all. As it stands expressions such as `a == b` will have vastly different bevahiour depending on the order and types of `a` and `b` whereas they might expect to be symmetrical. `operator==` only really makes sense for types with value semantics. – CB Bailey Nov 19 '09 at 18:13
0

If you don't care about comparisons of type A to type B, or B to C, etc. then you can simply implement an overloaded equality operator for each class:

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

That's dangerous though, because if you derive a new class D from B or C, you're going to have problems.

Otherwise you need to implement some comparators with a lot of dynamic_cast<>-ing to really do it right. Alternatively you could implement a function to create a hash code for each object and leverage that, e.g.

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

If you incorporate the object's type into the hash code in some fashion (not shown above) then you can also dispense with the silly typeid() comparisons above.

Rob Pelletier
  • 327
  • 2
  • 3
0

If you don't mind the base class referring to the sub-classes then double-dispatch:

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

Does it without requiring dynamic_casts.

jon hanson
  • 8,722
  • 2
  • 37
  • 61