One big remark, as mentioned above, when looking at Base *
it point to type Base
. I'm not using this notation in order to simplify the explanation.
What you are asking is a different question: what is the difference between the static type and the dynamic/runtime type of an object.
Let's observe the following code:
#import <iostream>
class Base
{
public:
virtual void foo()
{
std::cout << "From Base" << std::endl;
}
};
class A : public Base
{
public:
void foo() override
{
std::cout << "From A" << std::endl;
}
void some_A_only_function()
{
std::cout << "some function from A" << std::endl;
}
};
class B : public Base
{
public:
void foo() override
{
std::cout << "From B" << std::endl;
}
void some_B_only_function()
{
std::cout << "some function from B" << std::endl;
}
};
int main()
{
Base base{};
Base * a = new A();
Base * b = new B();
base.foo();
a->foo();
// a->some_A_only_function(); this won't compile: error: no member named 'some_A_only_function' in 'Base'
b->foo();
// b->some_B_only_function(); this won't compile either!!
}
And the output is:
From Base
From A
From B
All the objects here are from a static type of Base
, thus, you cannot call any function that isn't part of Base
, since the compiler cannot know which type is put in there in runtime. If we have overridden functions, the appropriate one will be called according to the runtime type (this is called dynamic dispatch).
To sum it up, there are 3 objects here:
- An object of static type
Base
and dynamic type Base
.
- An object of static type
Base
and dynamic type A
.
- An object of static type
Base
and dynamic type B
.
The only way to use the functions of A
or B
respectively, is by using casting in order to "return" them into their original form. But it should be done with caution!
Big remark: This is actually true in any language that offers polymorphism (as far as I know). It isn't unique to C++!