Jump to content
  • Advertisement
Sign in to follow this  
Ryan_001

C++ dynamic_cast

This topic is 1153 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

While looking around for a faster way to perform dynamic_cast's I came across this: https://github.com/DigitalInBlue/Priori.  The general idea came from a Stroustrup paper not too long ago where every base class is given a unique prime number id, and then a simple modulo operation can determine the legitimacy of a dynamic_cast.  Seemed simple enough but from my understanding of dynamic_cast is that pointer adjustments sometimes must be made as well.  At its core the code posted seems to simply use reinterpret_cast.

template<class T, class V> T priori_cast(V base) 
{ 
	if(base != nullptr)
	{
		// If it is convertable to the base class or to itself, return
		if(std::is_convertible<std::remove_pointer<V>::type, std::remove_pointer<T>::type>::value == true)
	    {
			return reinterpret_cast<T>(base);
		}

		const auto factor = priori::get(typeid(std::remove_pointer<T>::type));

		if((factor != 0) && (base->priori(factor) == true))
		{
			return reinterpret_cast<T>(base);
		}
	}

	return nullptr; 
}

This doesn't seem kosher...  Any thoughts on this?

Edited by Ryan_001

Share this post


Link to post
Share on other sites
Advertisement

Aside from using static_cast instead, the first condition for the up-cast check can also be turned from a run-time to a compile-time condition:

template<typename T, typename V>
class Conversion
{
	template<bool isDirectlyConvertible = true>
	static T ConvertHelper(V base)
	{
		return static_cast<T>(base);
	}
	
	template<>
	static T ConvertHelper<false>(V base)
	{
		if(base != nullptr)
		{	
			static const auto factor = priori::get(typeid(std::remove_pointer<T>::type)); // see remarks about static keyword here

			if((factor != 0) && (base->priori(factor) == true))
			{
				return static_cast<T>(base);
			}
		}
		
		return nullptr;
	}

public:

	static T Convert(V base)
	{
		return ConvertHelper<std::is_convertible<std::remove_pointer<V>::type, std::remove_pointer<T>::type>::value>(base);
	}
}

template<class T, class V> T priori_cast(V base) 
{ 
	return Conversion<T, V>::Convert(base);
}

Now there is one less condition to check per cast, also there doesn't need to be a nullptr-check in case you cast upwards with this cast. (code has not been tested and might fail due to small syntax errors, also I'm not 100% sure if you can even specialize functions like I did with ConvertHelper, otherwise you need to use another helper class).

 

Also, I'm kind of confused by their benchmarks. Looking at their implementation of "get", its somewhat compilacted, requiring a map-lookup and a thread-lock in a multithreaded environment. Even though dynamic_cast can be slow, I doubt that a map-lookup would be much faster. So I heavily doubt their benchmark parameters and would certainly benchmark myself before drawing any conclusions. Aside from that, you *might* improve on it by making the base-type-factor aquiration static inside the cast-method:

static const auto factor = priori::get(typeid(std::remove_pointer<T>::type));

Since from what I've seen the type-number should change at runtime (if it does, pardon me). Though in any case, if you want this to improve performance, I sure would test it beforehands.

Edited by Juliean

Share this post


Link to post
Share on other sites

True enough, but it is begging the question, assuming the need is there without touching on its validity.

 

There shouldn't be much of a need for the conversion or the type comparison in the first place.  

 

The fact that you would want the test in the first place is the far bigger problem.

 

If you are asking "Does the class implement a specific interface?" that isn't necessarily bad (but could still probably be avoided).  That's a dynamic cast to a base class.  For all the major compilers that comparison is already implemented and inexpensive, it is a direct pointer comparison that immediately succeeds or fails.

 

If you are going the other way, asking about any child class, or anywhere else on the inheritance hierarchy, I would question what you are doing where you think that is necessary. There shouldn't be any reason to know the concrete class or convertibility between anything other than the base interface.

Share this post


Link to post
Share on other sites

If you are going the other way, asking about any child class, or anywhere else on the inheritance hierarchy, I would question what you are doing where you think that is necessary. There shouldn't be any reason to know the concrete class or convertibility between anything other than the base interface.

 

Yeah, I won't argue that doing dynamic_cast to a child class is not the best idea. However, on some instances it might be the cleanest solution. While I try not to use it in my own project, in my latest graduation-team project we were doing this kind of stuff all the time. The project was written in Unreal 4, and seeing from the tutorials/Unreal Tournament source this is very common there.

 

One main reason to use this in their case was that there where a lot of base-functions like "UPawn::GetController", which would return a "UPlayerController" object. This object has a lot of default behaviour for controlling a player, but in reality you would have to create a subclass of UPlayerController, like MyCustomPlayerController. Now if you want to invoke any specific behaviour on a controller you get from a Pawn, even if you know what it is going to be, you still have to cast. Technically you could use a static_cast for this, but it might also easily be possible that there are multiple subtypes of unrealted custom player controllers. And this pretty much applies to their entire base game structure: You have lots of stuff given for you, but in the end you create your base classes and need to cast them to their specific types at a certain point in time.

 

The other reason was partially simply for convenience, for specifying behaviour at some points. One example would be when taking damage, the player has to react differently based on the type of damage event (which, in Unreals case is represented by a different class):

            if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
            {
                // point damage event, pass off to helper function
                FPointDamageEvent* const PointDamageEvent = (FPointDamageEvent*)&DamageEvent;

                // K2 notification for this actor
                if (Damage != 0.f)
                {
                    ReceivePointDamage(Damage, DamageTypeCDO, PointDamageEvent->HitInfo.ImpactPoint, PointDamageEvent->HitInfo.ImpactNormal, PointDamageEvent->HitInfo.Component.Get(), PointDamageEvent->HitInfo.BoneName, PointDamageEvent->ShotDirection, EventInstigator, DamageCauser);
                    OnTakePointDamage.Broadcast(Damage, EventInstigator, PointDamageEvent->HitInfo.ImpactPoint, PointDamageEvent->HitInfo.Component.Get(), PointDamageEvent->HitInfo.BoneName, PointDamageEvent->ShotDirection, DamageTypeCDO, DamageCauser);
                    SpawnTakenDamageEffects(PointDamageEvent->HitInfo);
                    PlayHitSound();
                }
                
            }
            else if (DamageEvent.IsOfType(FRadialDamageEvent::ClassID))
            {
                // radial damage event, pass off to helper function
                FRadialDamageEvent* const RadialDamageEvent = (FRadialDamageEvent*)&DamageEvent;

                // K2 notification for this actor
                if (Damage != 0.f)
                {
                    FHitResult const& Hit = (RadialDamageEvent->ComponentHits.Num() > 0) ? RadialDamageEvent->ComponentHits[0] : FHitResult();
                    ReceiveRadialDamage(Damage, DamageTypeCDO, RadialDamageEvent->Origin, Hit, EventInstigator, DamageCauser);
                    SpawnTakenDamageEffects(Hit);
                    PlayHitSound();
                }

            }

There are a ton of more examples like this. Sure, some of them could be solved by making a common base class with a virtual method and ie. passing the player pawn in. However, this also has a downside of potentially breaking encapsulation (if this specified behaviour needs to access otherwise internal methods/attributes of the target class), and possibly violates the SRP-principle.

 

 

Now again, I'm not saying there are no ways around this. I'm also not arguing that this is the best possible design for high-level game coding. However, it did its job, was quite easy to work with (both for our more or less experienced team members), and seeing that this is done/required in a professional environment like Unreal... Its certainly a possible way to do it, and I'm sure that there is a ton more examples of this used "professionally" (for high level work, that is).

Edited by Juliean

Share this post


Link to post
Share on other sites

I'm sorry Frob but I disagree whole-heartedly with the notion that dynamic_cast's are inherently bad; and I could come up with dozen of cases where they are a great solution given the problem constraints.  But that said I don't have a problem in mind.  I had some time and thought I'd take a look at what people were doing, surfing the web and came across what seemed to be an implementation of a Stroustrup paper.  There was no ulterior motive.  But really we should be open to talking about these concepts, even if its simply to know why its not a good idea.  A good craftsman needs to know his tools.

 

@SmkViper: my thoughts were similar.  reinterpret_cast (from my understanding) doesn't actually change the underlying pointer and should fail under many circumstances.  static_cast should work in the absence of virtual inheritence, but of course would break with virtual inheritance.

 

@Juliean: constant folding is one of the simplest and most ubiquitous forms of compiler optimization, I think you'd be hard pressed to find a compiler that wouldn't optimize out the run-time check.  Also the type-number needs to be constant for a given type (ie. all objects of the same type would get the same type-id).  As far as performance, the MSVC dynamic_cast I've read in the past did string comparison's on the type names (that's like a Rube-Goldberg implementation), and I'm pretty sure it still does in some cases (ie. across .dll boundaries) but whether it does now in all cases I am unsure.  Using a shared_mutex and a map lookup wouldn't be terrible, but of course no where near as fast static_cast's or normal virtual dispatch.

Share this post


Link to post
Share on other sites

 
You show a giant tree of:
if (DamageEvent.IsOfType(A::ClassID)) {}
else if (DamageEvent.IsOfType(B::ClassID)) {}
else if (DamageEvent.IsOfType(C::ClassID)) {}
...
else if (DamageEvent.IsOfType(Z::ClassID)) {}


That type of pattern breaks down terribly over time.

Instead of modifying your one class where the behavior actually changed, you are modifying everywhere else in the code that uses the thing.

When you make a change, you need to search all the code to find all the places everywhere that you looked up that type. If anyone creates a new type they need to hunt through all the code everywhere to ensure the new situation is handled. If anyone creates a new subtype, that also needs to be adjusted everywhere. If you need to change the behavior you cannot change it in one location, you need to hunt through the entire code base and look for all the if/else trees, all the switch statements, all the other type comparisons, and pray that you found and adjusted all of them.

It gets incredibly nasty very quick.

There are many different alternatives, and you do present polymorphism / virtual functions as one solution. Far better approaches are virtual functions like you mentioned, or the Visitor pattern, or the Strategy pattern, and more.

In one game I worked on (Tiger Woods), a particular bit of game state information was scattered across about fifty files in the way you mentioned above. It started small, but people had just copy/pasted the bad solution over and over and over. Attempting to make a change to the game state flow was difficult. We took the time to rewrite that in all the different classes into a more direct polymorphic solution. We spent several weeks untangling all the little oddities people had added in each different version, and consolidated many of the different hacks that had been added as four of them solved an identical bug in one way, others solved the same exact bug in a different way, and yet others still had the same bug.

An if/else tree where you modify behavior based on object type is less of a code smell, more of a code stench.

 

Well of course that's bad; but that doesn't even use dynamic_cast.  Sure there are tons of ways it can be misused; but what does it have to do with this thread?  Just cause others have misused it in the past doesn't mean we shouldn't understand how to use it properly??  I don't understand the line of thinking that takes you from 'Stroustrup presented a paper on dynamic_cast, this implementation of said paper seems weird'/'dynamic_cast is slow' to 'in this one project some idiot programmers made a terrible else-if tree'?

 

I'm not arguing that it can't be misused... its just it has nothing to do with this thread.

Share this post


Link to post
Share on other sites

@SmkViper: my thoughts were similar.  reinterpret_cast (from my understanding) doesn't actually change the underlying pointer and should fail under many circumstances.  static_cast should work in the absence of virtual inheritence, but of course would break with virtual inheritance.


If there's anything you should be using less of than dynamic_cast it's virtual inheritance. (Though multiple inheritance of non-interfaces is up there too).

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!