Why use static_cast rather than dynamic_cast in here?
I copy the following text from the book More Effective C++.
Item 31: Making functions virtual with respect to more than one object.
class GameObject { ... };
class SpaceShip: public GameObject { ... };
class SpaceStation: public GameObject { ... };
class Asteroid: public GameObject { ... };
The most common approach to double-dispatching returns us to the unforgiving world of virtual function emulation via chains of if-then-elses. In this harsh world, we first discover the real type of otherObject, then we test it against all the possibilities:
void SpaceShip::collide(GameObject& otherObject)
{
const type_info&开发者_运维技巧; objectType = typeid(otherObject);
if (objectType == typeid(SpaceShip)) {
SpaceShip& ss = static_cast<SpaceShip&>(otherObject);
process a SpaceShip-SpaceShip collision;
}
else if (objectType == typeid(SpaceStation)) {
SpaceStation& ss =
static_cast<SpaceStation&>(otherObject);
process a SpaceShip-SpaceStation collision;
}
...
}
Here is the question:
Q1> Why we use static_cast here rather than obvious dynamic_cast?
Q2> Are they same in this case?
thank you
// updated //
In fact, I am more interested in question 2.
For example,
class base {};
class subclass : public base {};
base *pSubClass = new subclass;
subclass *pSubClass1 = static_cast<subClass*> (pSubClass);
// does the static_cast do the job correctly in this case although I know we should use dynamic_cast here?
For the record, here is the idiomatic way of doing that:
void SpaceShip::collide(GameObject& otherObject)
{
if (SpaceShip* ss = dynamic_cast<SpaceShip*>(&otherObject)) {
// process a SpaceShip-SpaceShip collision;
}
else if (SpaceStation* ss = dynamic_cast<SpaceStation*>(&otherObject)) {
// process a SpaceShip-SpaceStation collision;
}
// ...
}
It's shorter, exhibits identical performance characteristics, and again, most importantly, is idiomatic C++ that won't make other programmers scratch their heads and wonder what the point is.
EDIT (in response to the OP's edit):
Yes, that is well defined behavior. Here's what the C++03 standard says, §5.2.9/8:
An rvalue of type “pointer to cv1
B
”, whereB
is a class type, can be converted to an rvalue of type “pointer to cv2D
”, whereD
is a class derived fromB
, if a valid standard conversion from “pointer toD
” to “pointer toB
” exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, andB
is not a virtual base class ofD
. The null pointer value is converted to the null pointer value of the destination type. If the rvalue of type “pointer to cv1B
” points to aB
that is actually a sub-object of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the result of the cast is undefined.
You've already verified the types yourself, so you don't need to use dynamic_cast. Dynamic_cast will check the types for you automatically.
Why they chose to implement it this way, instead of the more traditional dynamic_cast
I can't say, but the behavior of the two options is not necessarily the same. As written, that code only considers the actual type of the parameter, while dynamic_cast
considers where the parameter falls in an inheritance tree. Consider:
struct Base { virtual ~Base() { } };
struct Intermediate : Base { };
struct Derived : Intermediate { };
int main() {
Intermediate i;
Base* p_i = &i;
Derived d;
Base* p_d = &d;
assert(typeid(*p_i) == typeid(Intermediate)); //1
assert(dynamic_cast<Intermediate*>(p_i)); //2
assert(typeid(*p_d) == typeid(Intermediate)); //3
assert(dynamic_cast<Intermediate*>(p_d)); //4
}
(1) and (2) both pass their assertions, but (3) fails while (4) succeeds. p_d
points to a Derived
object, so type_id
yields information for a Derived
object, which will not compare equal to the information for an Intermediate
object. But Derived
derives from Intermediate
, so dynamic_cast
will happily convert a pointer to Derived
to a pointer to Intermediate
.
To put it in terms used in the original question, if otherObject
is a Frigate
, which derives from SpaceShip
, it will not use the "spaceship<->spaceship" collision routine. There's a good chance this is not the intended behavior; you might want Frigate
to use that code, but instead you have to manually add an explicit check for that new type.
Of course, if you're only checking against types that are never inherited from, this difference goes away. Or if you just don't want polymorphic behavior (although that would make the heading somewhat misleading). In that case, this might be more performant, but that's a giant implementation detail and I certainly wouldn't put money on it in practice.
Another small, and largely inconsequential, difference occurs if the types are not polymorphic. In my above code, if you remove the virtual destructor from Base
, (2) and (4) now exhibit undefined behavior. (1) and (3) remain well defined, but are now worthless; both will fail because typeid(*p_i)
will yield information about Base
rather than Intermediate
like it used to.
This seems like a pretty solid answer. Basically static cast is faster but doesn't do runtime type checking.
Some compilers will generate codes that throws std::bad_cast
if dynamic_cast
fails. So in this case the two approaches are different. Using dynamic_cast
may looks like
try {
SpaceShip& ship = dynamic_cast<SpaceShip&>(otherObject);
// collision logic
return;
} catch (std::bad_cast&) {}
try {
SpaceStation& station = dynamic_cast<SpaceStation&>(otherObject);
// collision logic
return;
} catch (std::bad_cast&) {}
that looks really bad.
First, I think it's important to note that Myers is presenting this code as the first strawman solution for double dispatch before moving on to superior solutions that are not dependent on RTTI.
To answer the second question first, yes, this is equivalent to implementations using dynamic_cast.
static_cast
is used here because we have already established that the object is of the targetted type, and thus don't need to pay for run-time checking again.
So why not use dynamic_cast
in the first place?
My suspicion is that Myers wrote it this way because this was going to be a chain of an indefinite number of if ()
checks. He could have done something like @ildjarn suggests, but that would have involved declaring a new variable for every type he wanted to check it against. My suspicion is he just liked the aesthetics of what he put up better.
Maybe I'm mistaken, but ... my understanding is that all rtti implementations involve some kind of lookup/search to find the type of an object passed to dynamic_cast or typeinfo.
Barring quantum effects, this search must take a measurable number of cycles to complete and, in the OP's code, the search result is cached, while in the dynamic_cast examples the search is repeated in each conditional.
Therefore the cached version must be faster. Keeping in mind caveats about premature optimization, I think it is also easy on the eye.
Nicht war?
PS: Tried this and it doesn't work. Hmmm. Anybody suggest why?
精彩评论