Does dynamic_cast really work for multiple inheritance?
I wanted to see if it's possible to create "interfaces", inherit them, and then check at runtime if any random class implements that interface. This is what I have:
struct GameObject {
int x,y;
std::string name;
virtual void blah() { };
};
struct Airholder {
int oxygen;
int nitrogen;
};
struct Turf : public GameObject, public Airholder {
Turf() : GameObject() {
name = "Turf";
}
void blah() { };
};
void remove_air(GameObject* o) {
Airholder* a = dynamic_cast<Airholder*>(o);
if(!a) return;
a->oxygen = 0;
a->nitrogen = 0;
};
Now, it works. The documentation says that it works, the test example works.. But also, it didn't compile until I added a virtual method to GameObject. The thing is, I really don't know if the feature is intended to be used like that. What made me wonder there i开发者_开发百科s the fact that I have to declare a virtual function for the class I'm checking. But obviously, there is none, the class I'm checking itself has no virtual functions, in fact my whole code has nothing to do with virtual functions, it's an entirely different approach.
So, I guess my question is: If what I'm doing really works, why do I need a virtual function to give my class a vtable? Why can't I declare the class a "runtime type" or something without virtual functions?
§ 5.2.7 of the standard says:
- The result of the expression dynamic_cast(v) is the result of converting the expression v to type T. T shall be a pointer or reference to a complete class type, or “pointer to cv void”. Types shall not be defined in a dynamic_cast. The dynamic_cast operator shall not cast away constness (5.2.11).
- If T is a pointer type, v shall be an rvalue of a pointer to complete class type, and the result is an rvalue of type T. If T is a reference type, v shall be an lvalue of a complete class type, and the result is an lvalue of the type referred to by T.
- If the type of v is the same as the required result type (which, for convenience, will be called R in this description), or it is the same as R except that the class object type in R is more cv-qualified than the class object type in v, the result is v (converted if necessary).
- If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type R.
If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B sub-object of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type “cv2 D” such that B is a base class of D, the result is an lvalue for the unique60) B sub-object of the D object referred to by v. In both the pointer and reference cases, cv1 shall be the same cvqualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D. [Example:
struct B {};
struct D : B {};
void foo(D* dp)
{
B* bp = dynamic_cast(dp); // equivalent to B* bp = dp;
}
—end example]- Otherwise, v shall be a pointer to or an lvalue of a polymorphic type (10.3).
And to make a type polymorphic, it needs a virtual function, as per § 10.3:
Virtual functions support dynamic binding and object-oriented programming. A class that declares or inherits a virtual function is called a polymorphic class.
So the reason why is "because the standard says so." That doesn't really tell you why the standard says so though, but the other answers cover that well I think.
So, I guess my question is: If what I'm doing really works, why do I need a virtual function to give my class a vtable? Why can't I declare the class a "runtime type" or something without virtual functions?
The presence of a virtual function is what makes a class polymorphic in C++. dynamic_cast<>
only works with polymorphic classes. (The compiler will reject a dynamic cast on a non-polymorphic object.)
Polymorphism has a cost, both in time and in space (memory). Calls to virtual functions are now indirect, typically implemented in terms of a virtual table. In some critical places, those costs are simply unacceptable. So the language provides means of avoiding these costs.
Similar concepts exist elsewhere in the language. The underlying principle is that if you don't want to use some high-falutin' feature you shouldn't have to pay for the fact the some people do want to use it.
dynamic_cast
requires the type to be polymorphic, and without any virtual methods (or at least a virtual destructor) a type is not (run-time) polymorphic. Simple inheritance is not enough. The run-time type information used by dynamic_cast
is stored alongside the vtable if remember correctly.
There are two main reasons. The first is that there's just no use case for it. The point of inheritance is virtual functions. If you're not using virtual functions, don't use inheritance.
The second is that it's very complex to actually implement dynamic_cast
that works without virtual functions due to the C++ compilation model. The only way to realistically implement dynamic_cast
is to operate on the virtual table- a binary blob of data is typeless. You could define a class and then only dynamic_cast
it in one TU- now one TU thinks the class has a vtable and one doesn't. That would be instant bad. Allowing dynamic_cast
on classes that do not already have virtual functions would be, well, export
, which means "Exceedingly difficult to implement".
As others have said, you need at least one virtual function to make a class polymorphic. Why this matters is that dynamic_cast itself is a polymorphic operation! Given a base class pointer, it returns different results based on the actual object it is called on.
C++ has a "don't pay for what you don't need" philosophy, thus the vtable (or whatever mechanism the compiler uses) is not provided unless there's a need as determined by the presence of a virtual function. Evidently the designers of C++ thought this was a reasonable requirement for the proper operation of dynamic_cast or they would have provided a way to generate a vtable without it.
[EDIT] According to the comments (people way smarter than me) my answer is completely wrong. However, make your destructors virtual anyway. [/EDIT]
In C++, I consider upcasting to a base type is only safe if the destructor is virtual. Technically it's safe, but in reality, you almost always want a virtual destructor. For instance:
class Base {
int thingy;
};
class Derived : Base{
int *array;
Derived() {array = new int[100];}
~Derived() {delete [] array;}
};
int main() {
std::auto_ptr<Base> obj(dynamic_cast<Base*>(new Derived));
}
In this example, when obj goes out of scope, the auto_ptr automatically calls the Base's destructor, but does not call the Derived deconstructor because the type is a Base, not a Derived. [Edit: corrections] This causes Undefined behaviour (at the very best, it causes a memory leak). I haven't any idea why C++ doesn't require a virtual destructor to compile down casts, it really should.
精彩评论