views:

156

answers:

5

I have a base class

class Animal 

with pure virtual functions, and a set of derived classes

class Monkey : public Animal 
class Snake : public Animal

I want to implement a comparison operation so that, if I encounter two pointers to Animals in my code

Animal* animal1
Animal* animal2

I can compare them to each other. The comparison should yield false, if animal1 and animal2 are of different derived classes. If they are of the same derived class, the output of the comparison operator should be returned.

Can someone point me to a good way of implementing this?

+3  A: 

Since there is no static type information associated with the two pointers, you will need to use RTTI. You can compare the results of type typeid operator to determine if the objects are of the same type.

An alternative would be to add your own type ID to the Animal class. Add another virtual function and have derived classes return something that uniquely identifies the type. You could use an enumeration, or maybe the name of the type as a string. If you can use it, though, RTTI would be much better IMHO.

Tim Yates
+1. In addition - RTTI incurs some runtime overhead (obviously), though not too much. If your application has tons of classes and you only need RTTI in just a few places, I would go with whooping your own solution using a dedicated function for type identification. If RTTI usage is commonplace, though - you should definitely use it over using your own method.
Eldad Mor
I think double dispatch might be a better way to do this than manually fiddling with `std::type_info` objects. What are you going to do once you know it's the right type anyway? You still would have to cast the pointer to a derived class' pointer. And since C++ sports only minimal RTTI, you probably would have to switch over a type. And a switch over a type is usually a strong indication that someone had better used `virtual` functions.
sbi
@sbi Agreed, actually. I have spent far too much time begrudgingly programming "The Java Way"(tm). It has obviously affected my C++.
Tim Yates
Does someone have a double dispatch solution to this that actually works? The one below does not.
imaginaryboy
+2  A: 

Use type_info class. It defines operator== which return whether the two types describe the same type. Here you can find reference: http://www.cplusplus.com/reference/std/typeinfo/type_info/

ldanko
+3  A: 

One way to implement this, is to use double-dispatch to differentiate between 'same class' and 'different classes':

class Monkey;
class Snake;

class Animal {
public:
  virtual bool compare_impl(const Animal*) const { return false; }
  virtual bool compare_impl(const Monkey*) const { return false; }
  virtual bool compare_impl(const Snake*) const { return false; }
  virtual bool compare(const Animal* rhs) const =0;
};

class Monkey : public Animal {
private:
  /* Override the default behaviour for two Monkeys */
  virtual bool compare_impl(const Monkey*) const { /* compare two Monkey's */ }
public:
  /* Let overload-resolution pick the compare_impl for Monkey and let virtual dispatch select the override in the dynamic type of rhs */
  virtual bool compare(const Animal* rhs) const { return rhs->compare_impl(this); }
};

class Snake : public Animal {
private:
  /* Override the default behaviour for two Snakes */
  bool compare_impl(const Snake*) const { /* compare two Snakes */ }
public:
  /* Let overload-resolution pick the compare_impl for Monkey and let virtual dispatch select the override in the dynamic type of rhs */
  virtual bool compare(const Animal* rhs) const { return rhs->compare_impl(this); }
};
Bart van Ingen Schenau
Can you explain what exactly the line using Animal::compare_impl; means? So far, I've seen the keyword "using" only in "using namespace foo"
Hans
@Hans: It brings a base class' identifier into the derived class' scope, preventing a derived class overload from hiding the base class function.
sbi
His 'using' declarations are doing two things: 1) changing the access of the base class `compare_impl` from protected to private, and 2) making Animal::compare_impl accessible since it was otherwise hidden by the derived class's `compare_impl` declarations.
imaginaryboy
Oh now I get the trick that inside the compare functions, the sides are reversed (this->compare_impl(rhs) becomes rhs->compare_impl(this)). The only thing that's bothering me about this solution is that I need to put the very same function compare into every single derived class. Is there no way to avoid this?
Hans
@Hans: You can, but then you have to include a `virtual bool compare_impl(const Derived*) const { return false;}` member-function in `Animal` for every derived class. This has the bigger disadvantage that `Animal` suddenly has to know about all the derived classes. The two things that make this technique work are first the reversal of the arguments (first function calls a member of the argument) and second that all relevant overloads are present where the reverse-call is made.
Bart van Ingen Schenau
Incidentally, this solution doesn't compile with GCC 4.2.1 reporting that Animal::compare_impl is protected within the context of the two derived compare methods. Even if it did compile, Animal::compare_impl isn't virtual, so the two derived compare methods are calling the Animal::compare_impl method that immediately returns false, and so every comparison will always return false.
imaginaryboy
imaginaryboy is right. Your solution doesn't compile due to the protected-error. I tried just making compare_impl in Animal public - this compiles, but any call to compare yields false.
Hans
It seems my trick for avoiding the dependence of Animal on its derived classes does not work. I have edited my answer with code that should work.
Bart van Ingen Schenau
A: 

Here is a little trick I use (I hope it can work for you too). I add the following private method to Animal and OVERRIDE it in every derived class (I know, it's a little bit of trouble, but it is faster than RTTI)

class Animal {
protected:

 virtual const void* signature() const 
 {
  static bool dummy;
  return &dummy;
 }
...
}


class Monkey : public Animal {
private:
 virtual const void* signature() const 
 {
  static bool dummy;
  return &dummy;
 }
...
}

now in order to see if 2 pointers (a and b) are of the same class just check for

a->signature()==b->signature()

It's not really a solution, it's a trick, but it works with just 2 virtual method calls (1 for each of the pointers) so it is rather fast.

frag
+4  A: 

Wow, a lot of the other answers were so totally unnecessary. dynamic_cast- it exists, use it.

class Animal {
public:
    virtual bool operator==(const Animal& other) = 0;
    virtual ~Animal() = 0;
};
template<class T> class AnimalComp : public Animal {
public:
    virtual bool operator==(const Animal& ref) const {
        if (const T* self = dynamic_cast<const T*>(&ref)) {
            return ((T*)this)->operator==(*self);
        }
        return false;
    }
    virtual bool operator!=(const Animal& ref) const {
        if (const T* self = dynamic_cast<const T*>(&ref)) {
            return ((T*)this)->operator!=(*self);
        }
        return true;
    }
};
class Monkey : public AnimalComp<Monkey> {
public:
    virtual bool operator==(const Monkey& other) const {
        return false;
    }
    virtual bool operator!=(const Monkey& other) const {
        return false;
    }
};
class Snake : public AnimalComp<Snake> {
public:
    virtual bool operator==(const Snake& other) const {
        return false;
    }
    virtual bool operator!=(const Snake& other) const {
        return false;
    }
};

Edit: Bow before my automatic templated implementation!

Edit edit: One thing I did do was forget to tag them as const, which was wrong of me. I will not apologize for not doing != as, let's face it, implementing it is a total doddle.

More edits: Jesus Christ guys, this is not an example on how to write != or ==, it's an example of how to use the CRTP. If you don't like how I chose to implement my != or ==, you can sue.

DeadMG
Yeah, better with the operator== overload.
imaginaryboy
imaginaryboy
@imaginaryboy: Yeah, I fixed it up to a much better solution.
DeadMG
That is very beautiful, thanks alot. One more question: Why is the destructor of Animal pure virtual? - this way it doesn't compile. If you replace that by virtual ~Animal() {} it works perfectly though.
Hans
It compiles fine on my compiler. :P Seriously, it doesn't really matter, I just made it pure virtual out of habit.
DeadMG
Why are the derived classes overriding `operator=()` to return false?
Remy Lebeau - TeamB
Because I don't know how to compare a pair of snakes?
DeadMG
Almost +1ed: If you had cautioned also about implementing `operator!=` for symmetry. Will do it once you update the post
Chubsdad
@Chubsdad: Just why? It's obvious how to implement it, and the OP didn't describe what semantics he wanted for !=.
DeadMG
@DeadMG: I am curios to know if there is a reasonable semantic reason why one should provide operator== and not operator!=. Also, I just mentioned about caution to implement operator!=, and not how to implement it (though that would also be nice)
Chubsdad
@chubsdad: There's no semantic reason, apart from the fact that I don't exist to hold the OP's hand and if he wants operator!= too he can go do it himself.
DeadMG
@DeadMG: Thanks. +1
Chubsdad
DeadMG: You would get my vote for this, if you A) would implement `!=` on top of `==` instead of repeating the same code, and B) replace the C-style casts (dropping a `const`, which, technically, might lead to UB) by `static_cast<>`.
sbi
@sbi: It's an example of how the CRTP can be used. The point is not in the semantics of == or !=. Really. I expect that the OP is perfectly capable of dropping in static_cast. I'm not his minder.
DeadMG
@DeadMG: Oh, I didn't look closely enough for to see CRTP at work! Sorry, I did now. (I still believe code made to teach something should be as perfect as possible, but I won't hold back on up-voting for that alone with the rest of the answer being that good.)
sbi