It's actually easy to understand if you imagine what exactly happens.
First of all, you have to understand that there are compile-time and runtime types. A variable is declared as some base type, but it may reference an object of a derived type during runtime.
Why not visa versa? It's easy. Because derived types are
extended, not reduced. Say, some base class "Base" has members A and B. A derived class "Derived" adds some member C, so it has A, B and C.
If your variable is of the compile-time type (is declared as) "Base", you are allowed to access A and B. When you reference "Derived", it also has C, but it still has A and B, so you are safe.
Imagine that you have the variable declared as "Derived", but you managed to assign an instance of "Base" to it. (Here, I don't make difference between assignment of pointers or instances themselves, the idea is the same.) Then the compiler can access not only A and B, but also C; and C does not exist during runtime. Imagine what can happen: you can access memory beyond the object.
The whole idea of OOP is based on working with derived classed referenced by variable of some base classes. To achieve polymorphism, virtual function mechanism is used. But in rare case we may need to violate "pure" OOP a bit, by having a base-type variable and interpreting the referenced object as an object of some derived type. Apparently, this is not always possible, so you first need to make sure that the actual runtime object is compatible with the type you want to interpret is as. (If not, you just skip the operations.) For this purpose, C++ has such a feature as
dynamic_cast:
Type conversions — C++ Tutorials — dynamic cast[
^].
This is also called
down-casting. I want to re-iterate: it should not be used on regular basis. Often, it's used as fix to some OOP design which initially did not take into account some requirements, that is, is incorrect, but it's not feasible to redesign it. There are other cases, but they should not be taken as regular technique.
—SA