views:

307

answers:

5

Can someone please put me out of my misery with this? I'm trying to figure out why a derived operator== never gets called in a loop. To simplify the example, here's my Base and Derived class:

class Base { // ... snipped
  bool operator==( const Base& other ) const { return name_ == other.name_; }
};

class Derived : public Base { // ... snipped
  bool operator==( const Derived& other ) const { 
    return ( static_cast<const Base&>( *this ) ==
             static_cast<const Base&>( other ) ? age_ == other.age_ :
                                                 false );
};

Now when I instantiate and compare like this ...

Derived p1("Sarah", 42);
Derived p2("Sarah", 42);
bool z = ( p1 == p2 );

... all is fine. Here the operator== from Derived gets called, but when I loop over a list, comparing items in a list of pointers to Base objects ...

list<Base*> coll;

coll.push_back( new Base("fred") );
coll.push_back( new Derived("sarah", 42) );
// ... snipped

// Get two items from the list.
Base& obj1 = **itr;
Base& obj2 = **itr2;

cout << obj1.asString() << " " << ( ( obj1 == obj2 ) ? "==" : "!=" ) << " "
     << obj2.asString() << endl;

Here asString() (which is virtual and not shown here for brevity) works fine, but obj1 == obj2 always calls the Base operator== even if the two objects are Derived.

I know I'm going to kick myself when I find out what's wrong, but if someone could let me down gently it would be much appreciated.

+9  A: 

That's because you haven't made your operator== virtual so the actual type is not taken into account at runtime.

Unfortunately, just making the operator== virtual is not going to solve your problem. The reason why is that when you change the function signature by changing the type of the argument from base to derived, you are actually creating a new function. It sounds like you want to look into double-dispatch to solve your problem.

R Samuel Klatchko
I don't believe you can actually make an operator virtual.
McWafflestix
@McWafflesitx - your belief is incorrect.
R Samuel Klatchko
@Samuel: I didn't know that, thank you for teaching me something today!
LiraNuna
Double-dispatch is just another name for virtual functions according to that Wikipedia link. Changing the function signature isn't really his problem, expecting C++ to choose an override based on dynamic type is.
Potatoswatter
@Potatoswatter - read that closer. The article contrasts double-dispatch with single-dispatch calls. They then note that single-dispatch calls are also known as virtual functions.
R Samuel Klatchko
@R: The article is terribly written, but to me the first paragraph says that single-dispatch calls in OO languages work like double-dispatch calls. Agreed that he needs to determine the types of all arguments to `operator==` but I disagree that spaghetti control-flow through dispatcher functions is the way to go.
Potatoswatter
@Potatoswatter - you are not correctly understanding the article. C++ virtual functions are single dispatch, the actual function is based on the runtime type of a single argument (the object or for operators, the left argument). Double-dispatch is not part of the language and requires the programmer to implement them.
R Samuel Klatchko
Agreed. I tried making it virtual but discounted this given that the parameters were different. It never occurred to me to make the Derived op== take a Base and cast internally. Also, I thought about double dispatch or something similar but assumed I was making things unecessarily complicated. Thanks.
Robin Welch
A usual workaround for double dispatch is the Visitor design pattern. Not that I have actually ever tried it, but it may be worth taking a look.
Nemanja Trifunovic
+2  A: 

You need to make operator== virtual and you need to make sure that they both methods have the same signature. i.e. they will likely need to both take Base. You could have an overloaded operator== in your derived class that would be able to handle Derived objects.

Jesse
A: 

For derived classes to use their own implementation of an operator the operator must be virtual in the base class, otherwise the base classes implementation will be used instead.

Mark
Trouble is, even when I made operator== virtual it had no effect as the signature was different. The clue, I think, is to make the Derived class implementation take a Base reference and cast internally since, in context it should always be called with other being a Derived.
Robin Welch
+1  A: 

When a member function is virtual, the virtual table is used at runtime to polymorphically call the function on the type that the pointer actually points to (in this case, your class Derived). When a function is not virtual, no virtual table lookup is done and the function the given type is called (in this case, your class Base).

Here, your operator=() functions are not virtual, so the type of the pointer is used rather than the type that the pointer points to.

Jonathan M Davis
+2  A: 

There are two ways to fix this.

First solution. I would suggest adding some extra type logic to the loop, so you know when you have a Base and when you have a Derived. If you're really only dealing with Derived objects, use

list<Derived*> coll;

otherwise put a dynamic_cast somewhere.

Second solution. Put the same kind of logic into your operator==. First make it virtual, so the type of the left-hand operand is determined at runtime. Then manually check the type of the right-hand operand.

virtual bool operator==( const Base& other ) const {
  if ( ! Base::operator==( other ) ) return false;
  Derived *other_derived = dynamic_cast< Derived * >( &other );
  if ( ! other_derived ) return false;
  return age_ == other_derived->age_;
}

but considering that objects of different types probably won't be equal, probably what you want is

virtual bool operator==( const Base& other ) const {
  Derived *other_derived = dynamic_cast< Derived * >( &other );
  return other_derived
   && Base::operator==( other )
   && age_ == other_derived->age_;
}
Potatoswatter
This makes sense. The 'real' application compares using smart pointers (not Boost unfortunately but a 'home brew' of the client I'm working with) which complicated things even more. I will try this out though. Thankfully although the collection uses base smart pointers, I can be reasonably sure because of the context, that the objects will all be of a common (derived) type. Thanks very much.
Robin Welch
Interestingly, when I tried this, I found that the chain to the base class operator== caused - because it was virtual - a call to the derived operator== (i.e. infinite recursion) Hmm...
Robin Welch
@Robin: sorry, fixed.
Potatoswatter
No problem. I'd taken to adding a protected static isEqual(lhs,rhs) which formed the basis of what I wanted to do and could be chained and called from op== - bit more clumsy though :(
Robin Welch