Suppose Y is a derived class from class X and X declares foo to be virtual. Suppose y is of type (Y*). Then ((X*)y)->foo() will execute the Y version of foo(), but ((X)*y).foo() will execute the X version. Can you tell me why polymorphism does not apply in the dereferenced case? I would expect either syntax would yield the Y version of foo().
I believe that's simply due to the way the language is specified. References and pointers use late binding wherever possible, while objects use early binding. It would be possible to do late binding in every case (I imagine), but a compiler that did that wouldn't be following the C++ specifications.
A cast always(*) creates a new object of the type you're casting to, which is constructed using the object you're casting.
Casting to X* creates a new pointer (that is, an object of type X*). It has the same value as y
, so it still points to the same object, of type Y.
Casting to X creates a new X. It is constructed using *y
, but it otherwise has nothing to do with the old object. In your example, foo()
is called on this new "temporary" object, not on the object pointed to by y
.
You are correct that dynamic polymorphism only applies to pointers and references, not to objects, and this is the reason why: if you have a pointer-to-X then the thing it points to might be a subclass of X. But if you have an X, then it's an X, and nothing else. virtual calls would be pointless.
(*) unless optimisation allows the omission of code that doesn't change the result. But optimisation isn't allowed to change what foo()
function is called.
I think Darth Eru's explanation is correct, and here is why I think C++ behaves that way:
The code (X)*y is like creating a local variable that is of type X. The compiler needs to allocate sizeof(X) space on the stack, and it throws away any extra data included in an object of type Y, so when you call foo() it has to execute the X version. It would be difficult for the compiler to behave in a way that would let you call the Y version.
The code (X*)y is like creating a pointer to an object, and the compiler knows that the object pointed to is X or a subclass of X. At runtime when you dereference the pointer and call foo with "->foo()" the class of the object is determined and the proper function is used.
The dereferencing (the *y
part) is fine, but the cast (the (X)
part) creates a new (temporary) object specifically of class X
-- that's what casting means. So, the object has to have the virtual table from class X
-- consider that the casting will have removed any instance members added by Y
in the subclassing (indeed, how could X
's copy ctor possibly know about them?), so it would potentially be a disaster if any of Y
's overrides were to execute -- secure in their knowledge that this
points to an instance of Y
, complete with added members and all... when that knowledge was false!
The version in which you cast pointers is of course completely different -- the *X
has just the same bits as the Y*
, so it's still pointing to a perfectly valid instance of Y
(indeed, it's pointing to y
, of course).
The sad fact is that, for safety, the copy ctor of a class should really only be called with, as argument, an instance of that class -- not of any subclass; the loss of added instance members &c is just too damaging. But the only way to ensure that is to follow Haahr's excellent advice, "Don't subclass concrete classes"... even though he's writing about Java, the advice is at least as good for C++ (which has this copy ctor "slicing" problem in addition!-)
You are slicing the Y
object part and copy the object into an X
object. The function then called is called on an X
object, and thus the function of X
is called.
When you specify a type in C++ in a declaration or cast, that is meant to say that the object declared or casted-to is actually of that type, not of a derived type.
If you want to merely treat the object is being of type X
(that is to say, if you want the static type of the expression be X
, but still want it to denote an Y
object) then you cast to a reference type
((X&)*y).foo()
This will call the function in the Y
object, and will not slice nor copy into an X
object. In steps, this does
- Dereference the pointer
y
, which is of typeY*
. Dereferencing yields an lvalue expression of typeY
. An lvalue expression can actually denote an object of a derived type, even if its static type is the one of its base. - Cast to a
X&
, which is a reference toX
. That will yield an lvalue expression of typeX
. - Call the function.
Your original cast did
- Dereference the pointer
y
. - The resulting expression casted to
X
. This will yield to a copy operation into a newX
object. The resulting expression of that is an rvalue expression of static typeX
. The dynamic type of the object denoted is alsoX
, as is with all rvalue expressions. - Call the function.