Jump to content
  • Advertisement
Sign in to follow this  
too_many_stars

Alternatives to dynamic_cast

This topic is 951 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi guys,

 

In his excellent book, 55 Specific ways to improve your programs and designs, Scott Meyers has the following to say about dynamic casting:

 

1.) "If you find yourself wanting to cast, it’s a sign that you could be approaching things the wrong way. This is especially the case if your want is for dynamic_cast." pg 120

 

2.) "many implementations of dynamic_cast can be quite slow" pg 120

 

3.) "Good C++ uses very few casts" pg 123

 

4.) "you should be especially leery of dynamic_casts in performance-sensitive code." pg 121

 

One of Scott's solutions is the following: "provide virtual functions in the base class that let you do what you need"

 

As I have up to this point relied heavily on dynamic_casts (especially the cascading types) I was wondering, given the following scenario...

class Person{};

class Student:public Person{};

class Employee:public Person{};

//later in the code


std::list<Person*> people;

for(auto it=people.begin();it!=people.end();it++){
 if((*it)->type==STUDENT_TYPE){
   //need to access student id number, classes etc. 
}
if((*it)->type==EMPLOYEE_TYPE){
  //need to access social insurance number

}

}

So my question is, how would one implement virtual functions in the "if statements", that allowed one to access data members (without dynamic_cast of course)

 

Thanks,

 

Mike

Share this post


Link to post
Share on other sites
Advertisement

Thanks Josh,

 

I like the composition idea, with the data separated out and still using the Person container. I will give it some thought and try to implement something clean.

 

Thanks,

 

Mike

Share this post


Link to post
Share on other sites
Regarding SOLID, above, your code violates the LSP rule, so it's not a valid use of inheritance to begin with -- which is why you end up in "not recommended" situations :)

Share this post


Link to post
Share on other sites

Put differently regarding the LSP rule, here is your problem:

 


for(auto it=people.begin();it!=people.end();it++){
if((*it)->type==STUDENT_TYPE){
//need to access student id number, classes etc.
}
if((*it)->type==EMPLOYEE_TYPE){
//need to access social insurance number
}

 

Instead, if you had followed the LSP, the could would be:

 

for(auto it=people.begin();it!=people.end();it++) {

  (*it)->DoTheThing();

}

 

Users of the class should not need to know anything about the implementation details.  If they are truly substitute objects then all of them have the same interface.

 

The class implements whatever variation there is within the object so that users of the class don't need to care.

Share this post


Link to post
Share on other sites

While I fully agree with "good code has few or no casts" (ie if you need them, double check you're not doing something wrong), I am always suspicious when the biggest argument against some concept is performance.
As such I don't see any explanation why dynamic_cast is so wrong in the quotes of Scott Meyers.

If you don't use dynamic_cast, you reconsidered it, and reached the conclusion you cannot eliminate it, you must use something else, which is not free either.

Edited by Alberth

Share this post


Link to post
Share on other sites

The performance penalty of RTTI is unavoidable, and unlikely to be lower than that of virtual function calls (which are a less general mechanism).

Virtual functions are also a much cleaner, safer and more principled way to "unmix" objects of different types that have been confused together (like your students and employees).

Of course, in many context the best solution for your example is maintaining separate lists of students and employees rather than a single list of people.

Share this post


Link to post
Share on other sites

Ideally you should structure your code so that always the derived class gets passed on, and from there you can cast to the base if necessary.

 

But there are times which, for multiple reasons, you need to downcast from base to derived; be it because of time constraints for good design, conflicting goals in the design or contradictory requirements, complex software, etc. And for some reasons virtual calls aren't a solution either.

 

In those exceptional cases, I usually do:

assert( dynamic_cast<Derived*>( basePtr ) );
Derived *myDerived = static_cast<Derived*>( basePtr );

This way my code will explode with an assert on debug mode if I somehow passed the wrong pointer; while still having zero overhead in release mode.

Though I usually pair it with something else, like:

assert( dynamic_cast<Derived*>( basePtr ) );
if( basePtr->getType() = DERIVED_TYPE )
     Derived *myDerived = static_cast<Derived*>( basePtr );

So that Release mode has very little overhead (a branch evaluating an int, which can have cache misses or misspredict) instead of the overhead of RTTI (which usually means string vs string comparison!)

The assert is still there just in case return value of getType() is incorrect.

Edited by Matias Goldberg

Share this post


Link to post
Share on other sites

A dynamic cast can be easily converted to a virtual function. For every "dynamic_cast<X*>", make a virtual function "X* GetX()", which returns "nullptr" in the base class, and "this" in the X class, eg (leaving out the declarations, constructors, etc).

class A {
    virtual B* GetB() { return nullptr; };
    virtual C* GetC() { return nullptr; };
};

class B : public A {
    virtual B* GetB() { return this; };
};

class C : public A {
    virtual C* GetC() { return this; };
};

Of course, the idea hasn't improved at all even if you code it in virtual functions.

 

For the above reason, I don't think dynamic_cast would be extremely expensive compared to virtual functions. A compiler could add this kind of code.

 

 

 

@Matias Goldberg

if( basePtr->getType() = DERIVED_TYPE )

That should be 2 '=' signs there :)

Share this post


Link to post
Share on other sites
A dynamic cast can be easily converted to a virtual function. For every "dynamic_cast<X*>", make a virtual function "X* GetX()", which returns "nullptr" in the base class, and "this" in the X class, eg (leaving out the declarations, constructors, etc).

 

 
Ignoring whether or not this approach allows for efficient implementation of all the dynamic_cast rules (I'm not convinced it does), it's not how most compilers actually implement dynamic_cast. Most of the time they use the type information graph they've already generated and shoved somewhere for other type introspection purposes (like typeid and the like) which can't be implemented with runtime code (because they must work for compile time checks) and walk that in some fashion.
 
For example, cl.exe turns dynamic_cast operations into calls to __RTDynamicCast, which is this monstrosity:
 
extern "C" PVOID __CLRCALL_OR_CDECL __RTDynamicCast (
    PVOID inptr,            // Pointer to polymorphic object
    LONG VfDelta,           // Offset of vfptr in object
    PVOID SrcType,          // Static type of object pointed to by inptr
    PVOID TargetType,       // Desired result of cast
    BOOL isReference)       // TRUE if input is reference, FALSE if input is ptr
    throw(...)
{
    PVOID pResult=NULL;
    _RTTIBaseClassDescriptor *pBaseClass;


    if (inptr == NULL)
            return NULL;


    __try {


        PVOID pCompleteObject = FindCompleteObject((PVOID *)inptr);
        _RTTICompleteObjectLocator *pCompleteLocator =
            (_RTTICompleteObjectLocator *) ((*((void***)inptr))[-1]);
#if (defined(_M_X64)) && !defined(_M_CEE_PURE)
        unsigned __int64 _ImageBase;
        if (COL_SIGNATURE(*pCompleteLocator) == COL_SIG_REV0) {
            _ImageBase = GetImageBase((PVOID)pCompleteLocator);
        }
        else {
            _ImageBase = ((unsigned __int64)pCompleteLocator - (unsigned __int64)COL_SELF(*pCompleteLocator));
        }
#endif


        // Adjust by vfptr displacement, if any
        inptr = (PVOID *) ((char *)inptr - VfDelta);


        // Calculate offset of source object in complete object
        ptrdiff_t inptr_delta = (char *)inptr - (char *)pCompleteObject;


        if (!(CHD_ATTRIBUTES(*COL_PCHD(*pCompleteLocator)) & CHD_MULTINH)) {
            // if not multiple inheritance
            pBaseClass = FindSITargetTypeInstance(
                            pCompleteLocator,
                            (_RTTITypeDescriptor *) SrcType,
                            (_RTTITypeDescriptor *) TargetType
#if (defined(_M_X64)) && !defined(_M_CEE_PURE)
                            , _ImageBase
#endif
                            );
        }
        else if (!(CHD_ATTRIBUTES(*COL_PCHD(*pCompleteLocator)) & CHD_VIRTINH)) {
            // if multiple, but not virtual, inheritance
            pBaseClass = FindMITargetTypeInstance(
                            pCompleteObject,
                            pCompleteLocator,
                            (_RTTITypeDescriptor *) SrcType,
                            inptr_delta,
                            (_RTTITypeDescriptor *) TargetType
#if (defined(_M_X64)) && !defined(_M_CEE_PURE)
                            , _ImageBase
#endif
                            );
        }
        else {
            // if virtual inheritance
            pBaseClass = FindVITargetTypeInstance(
                            pCompleteObject,
                            pCompleteLocator,
                            (_RTTITypeDescriptor *) SrcType,
                            inptr_delta,
                            (_RTTITypeDescriptor *) TargetType
#if (defined(_M_X64)) && !defined(_M_CEE_PURE)
                            , _ImageBase
#endif
                            );
        }


        if (pBaseClass != NULL) {
            // Calculate ptr to result base class from pBaseClass->where
            pResult = ((char *) pCompleteObject) +
                      PMDtoOffset(pCompleteObject, BCD_WHERE(*pBaseClass));
        }
        else {
            pResult = NULL;
            if (isReference)
#ifndef _SYSCRT
                throw std::bad_cast("Bad dynamic_cast!");
#else
                throw bad_cast("Bad dynamic_cast!");
#endif
        }


    }
    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
              ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH)
    {
        pResult = NULL;
#ifndef _SYSCRT
        throw std::__non_rtti_object ("Access violation - no RTTI data!");
#else
        throw __non_rtti_object ("Access violation - no RTTI data!");
#endif
    }


    return pResult;
}

That's decidedly more expensive than a virtual table dispatch even before we bother to examine what the various Find*TypeInstance functions do.

 
Edited by Josh Petrie

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!