I would not say "change type of object". C++ uses static, strong typing. An object is what an object is.
The runtime polymorphism is more about objects having
apparent type (common interface) and
true type (implementation, behaviour).
Lets do a simplified example with output:
1 2 3
|
void push( std::ostream & out ) {
out << 42;
}
|
The only thing that the push() requires is that there is operator<< that shovels 42 into ostream. The push() can be called on anything that apparently is an ostream.
1 2 3 4 5 6 7 8
|
using std::cout;
std::ostringstream foo;
std::ofstream bar( "bar.txt" );
// use push
push( cout );
push( bar );
push( foo );
|
The cout, foo and bar are three objects. They have different types. The type is set on creation, during compilation. They all inherit from ostream, so they fulfil the static requirements of the push(). What actually happens on shoveling the 42, is
different.
Too static?
1 2 3 4 5 6 7 8 9
|
int main( int argc, char ** argv ) {
if ( 1 < argc ) {
std::ofstream off( argv[1] );
push( off );
} else {
push( std::cout );
}
return 0;
}
|
The compiler can verify that both branches have correct typing, but it cannot know which true object types are actually created/used during each run of the program.
We could create a new type that inherits (directly or indirectly) from ostream and call the push() with object of that type. The result of "out << 42;" could again be different.
There is a way to check for "true" type and thus have access to additional members that the interface of the base class does not have:
1 2 3 4 5 6 7
|
void foo( Player * p ) {
auto m = dynamic_cast<Mage*>(p);
if ( m ) {
m->spell();
}
p->attack();
}
|
If the true type of object pointed to by p is Mage (or derived from Mage), then m is a valid pointer. Otherwise m has value nullptr. The Mage has to derive from Player, obviously.