Thread: Eliminating dynamic_cast<>

  1. #1
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36

    Eliminating dynamic_cast<>

    I have a polymorphic set of classes, and I want to have a virtual comparison function. Right now each of the derived classes has a function that looks something like this:

    Code:
    bool
    DerivedClass::Compare( const BaseClass* B ) const
        {
        if( const DerivedClass* derived = dynamic_cast<const DerivedClass*>(B) )
            return details_ == derived->details_ ;
        return false ;
        }
    Is it possible to eliminate the dynamic_cast, or am I stuck with it?

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Use an overloaded helper function;
    Code:
    class BaseClass
    {
         public:
    
               virtual bool Compare(const BaseClass *) const;
    };
    
    class DerivedClass : public BaseClass
    {
           public:
    
                 bool Compare(const BaseClass *) const;                // override inherited virtual function
                 virtual bool Compare(const DerivedClass *) const; // overload function of the same name
    };
    
    bool BaseClass::Compare(const BaseClass *b)   // implementation only required if BaseClass can be instantiated
    {
        // compare b with * this
    }
    
    bool DerivedClass::Compare(const BaseClass *b)
    {
         return b->Compare(this);    // this is a pointer to DerivedClass, so this calls the overloaded version
    }
    
    bool DerivedClass::Compare(const DerivedClass *b)
    {
         // both this and b point to a DerivedClass, so ...
    
        return details_ == b->details_;
    }
    This code above assumes that the pointers provided as arguments to the functions point at valid objects.

  3. #3
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    That's basically the self-visiting pattern. Main problem is that you have to adjust every class whenever you add a new one to the hierarchy.

    What you need is called double-dispatch. There is no inherent support for it in C++. You can search the web for the term to find various attempts at solving it.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #4
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    The double dispatch does use dynamic_cast (it is one of the few places where using dynamic_cast makes sense). So if you need different derived classes to be able to compare to each other then you're probably best off to use it.

  5. #5
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    There's more than one way to implement double dispatch. For example, through the self-visitor it doesn't use dynamic_cast:
    Code:
    class A;
    class B;
    
    class Base
    {
      friend bool operator == (const Base &lhs, const Base &rhs);
    protected:
      virtual bool dispatch_equals(const Base &rhs) const = 0;
      virtual bool do_equals(const A &lhs) const = 0;
      virtual bool do_equals(const B &lhs) const = 0;
    };
    
    bool operator ==(const Base &lhs, const Base &rhs)
    {
      return lhs.dispatch_equals(rhs);
    }
    
    class A : public Base
    {
    protected:
      virtual bool dispatch_equals(const Base &rhs) const {
        return rhs.do_equals(*this);
      }
      virtual bool do_equals(const A &lhs) const {
        // Implement
      }
      virtual bool do_equals(const B &lhs) const {
        // Implement
      }
    };
    
    class B : public Base
    {
    protected:
      virtual bool dispatch_equals(const Base &rhs) const {
        return rhs.do_equals(*this);
      }
      virtual bool do_equals(const A &lhs) const {
        return lhs.do_equals(*this);
      }
      virtual bool do_equals(const B &lhs) const {
        // Implement
      }
    };
    The problem is obvious: the base class must know about every derived class at compile time. That subverts a good part of the use cases of inheritance.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  6. #6
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    True, and the problem you speak of is why double dispatch might be better implemented with dynamic_cast, right?

  7. #7
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    No, you still need a list of all classes in the hierarchy. At best, you save some repetitive code in the class implementations, but I'm not even sure of that.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  8. #8
    The superhaterodyne twomers's Avatar
    Join Date
    Dec 2005
    Location
    Ireland
    Posts
    2,273
    >> Is it possible to eliminate the dynamic_cast, or am I stuck with it?
    Is there any reason why you want to eliminate the cast or is it just for the convenience?

    Couldn't you create a base comparison operator which is inherited? Or a function with the same properties:

    Code:
    class base {
    public:
      int details_;
    
      /* etc */
    
      template<typename type>
        bool operator == ( const type &inst ) {
          return details_ == inst.details_;
        }
    };
    
    
    class derr_1 : public base   {};
    class derr_2 : public base   {};
    class derr_3 : public derr_2 {}; // etc
    
    
    int main( void ) {
      base   thing_0;
      derr_1 thing_1;
      derr_2 thing_2;
      derr_3 thing_3;
    
      thing_0.details_ = 42;
      thing_1.details_ = 42;
      thing_2.details_ = 42;
      thing_3.details_ = 42;
    
      std::cout<< (thing_0==thing_1 ? "Same\n" : "Different\n");
      std::cout<< (thing_0==thing_2 ? "Same\n" : "Different\n");
      std::cout<< (thing_0==thing_3 ? "Same\n" : "Different\n");
    
      std::cout<< (thing_1==thing_2 ? "Same\n" : "Different\n");
      std::cout<< (thing_1==thing_3 ? "Same\n" : "Different\n");
      
      std::cout<< (thing_2==thing_3 ? "Same\n" : "Different\n");
    
      return 0;
    }
    Or am I missing something?

  9. #9
    pwns nooblars
    Join Date
    Oct 2005
    Location
    Portland, Or
    Posts
    1,094
    I was thinking a singleton that could take 2 of the base type and compare them, didn't think of the template option.

  10. #10
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    > Or am I missing something?

    That only works because the objects are essentially the same. In real code you would want to maintain the ability to compare objects in different ways, despite being related.

    To me it seems like template abuse, especially if bar objects were different from foo objects in significant ways. Then things get really hairy if another class quz implements just bar, and you want to compare those objects differently. You would need the flexibility of virtual functions to compare bar and quz objects differently from foo and bar objects, I beleive.

  11. #11
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    The template is nonsense there, anyway. Take const base& and be done, since all information is in the base class anyway. No need to generate redundant code.

    It's like this: either the comparison works with base data alone, in which case you can just give base the == operator.
    Or the comparison requires derived data (which, to me, suggests a fragile or hard-to-comprehend class design: sure, apple and orange both derive from fruit, but if you have to compare them, shouldn't the comparison be based on things that are generic fruit properties such as weight?) - then you need double dispatch of some sort.

    As I mentioned in the parenthesis, I think simple comparison should never require multiple dispatch. The classic example for double dispatch is collision detection between various shapes/objects.

    That said, Andrei Alexandrescu's Loki library contains various double dispatchers with differing trade-offs (speed, code size, modifying involved classes). The components are described in detail in Modern C++ Design; the library itself is freely available.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  12. #12
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    You don't need the list of the other derived classes if you are only comparing against yourself. If the two types are different, use the base class comparison (or whatever function), and if they are the same then use that class's function. Why does any class need to know about any other class?
    Code:
    class Base
    {
      friend bool operator == (const Base &lhs, const Base &rhs);
    protected:
      virtual bool dispatch_equals(const Base &rhs) const
      {
        // implement generic compare.
      }
    };
    
    bool operator ==(const Base &lhs, const Base &rhs)
    {
      return lhs.dispatch_equals(rhs);
    }
    
    class A : public Base
    {
    protected:
      virtual bool dispatch_equals(const Base &rhs) const {
        A* rhsA = dynamic_cast<A*>(&rhs);
        if (rhsA)
        {
          // implement A specific compare
        }
        return Base::dispatch_equals(rhs);
      }
    };
    
    class B : public Base
    {
    protected:
      virtual bool dispatch_equals(const Base &rhs) const {
        B* rhsB = dynamic_cast<B*>(&rhs);
        if (rhsB)
        {
          // implement B specific compare
        }
        return Base::dispatch_equals(rhs);
      }
    };

  13. #13
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    The point of the original question, Daved, was on how usage of dynamic_cast can be eliminated which your solution does not do. And the obvious approach to doing that is some variant of the self-visitor pattern. Using that pattern can introduce some need for all derived classes to be aware of each other; that is one of the trade-offs.

  14. #14
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    I thought the point was whether there was a better way that doesn't use dynamic_cast. I'm saying that using dynamic_cast is acceptable here and is better in many cases that forcing derived classes to be aware of each other. It doesn't matter if it is possible to remove the dynamic_cast if the alternative is worse.

  15. #15
    Sweet
    Join Date
    Aug 2002
    Location
    Tucson, Arizona
    Posts
    1,820
    Or you could do something gangsta like this
    Code:
    #include <iostream>
    
    enum enClassObjectType{
    	eClassBase,
    	eClassDerivedOne
    };
    
    class CBaseClass{
    public:
    	CBaseClass()
    	{
    		mObjectType = eClassBase;
    	}
    	virtual ~CBaseClass()
    	{
    	}
    public:
    	void SetDetails(const int value)
    	{
    		mDetails = value;
    	}
    
    	enClassObjectType GetObjectType() const
    	{
    		return mObjectType;
    	}
    	virtual bool operator==(const CBaseClass *pBaseClass) const
    	{
    		return compare_details(pBaseClass);
    	}
    protected:
    	bool compare_details(const CBaseClass *pBaseClass) const
    	{
    		return mDetails == pBaseClass->mDetails;
    	}
    	int mDetails;
    	enClassObjectType mObjectType;
    };
    
    class CDerivedClassOne : public CBaseClass
    {
    public:
    	CDerivedClassOne()
    	{
    		mObjectType = eClassDerivedOne;
    	}
    
    	virtual bool operator==(const CBaseClass *pBaseClass) const
    	{
    		if(pBaseClass->GetObjectType() == eClassDerivedOne){
    			return compare_details(pBaseClass);
    		}//if
    		return false;
    	}
    };
    
    int main()
    {
    	CBaseClass *pBaseClass = new CBaseClass;
    	CBaseClass *pChildClassOne = new CDerivedClassOne;
    	CBaseClass *pChildClassTwo = new CDerivedClassOne;
    
    	
    	pBaseClass->SetDetails(10);
    	pChildClassOne->SetDetails(10);
    	pChildClassTwo->SetDetails(10);
    
    	if(*pBaseClass == pChildClassOne){
    		std::cout<<"I should be here"<<std::endl;
    	}//if
    
    	if(*pBaseClass == pChildClassTwo){
    		std::cout<<"I should be here"<<std::endl;
    	}//if
    	
    	if(*pChildClassOne == pBaseClass){
    		std::cout<<"I should not be here"<<std::endl;
    	}//if
    
    	if(*pChildClassTwo == pBaseClass){
    		std::cout<<"I should not be here"<<std::endl;
    	}//if
    
    	if(*pChildClassOne == pChildClassTwo){
    		std::cout<<"I should be here"<<std::endl;
    	}//if
    
    	return 0;
    }
    But doing all these tricks to just get rid of dynamic_cast just seems like a waste of your time. Thats why it was invented.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 15
    Last Post: 05-06-2008, 05:02 AM
  2. Eliminating a window border
    By Night_Blade in forum Windows Programming
    Replies: 2
    Last Post: 08-26-2007, 09:15 AM
  3. Eliminating lighting "over-bright"
    By psychopath in forum Game Programming
    Replies: 1
    Last Post: 05-31-2006, 06:52 PM
  4. Eliminating duplicates
    By C-Struggler in forum C Programming
    Replies: 3
    Last Post: 03-23-2003, 11:12 PM
  5. eliminating dup values in array
    By Unregistered in forum C Programming
    Replies: 2
    Last Post: 04-19-2002, 06:05 PM