He wasn’t violating and you actually very much can. It’s called down casting. As long as the cast to the child class is valid, then there’s no problem and it conforms to the Liskov substitution principle. Here’s an example where it just works:
#include <iostream>
#include <memory>
class ParentClass {
public:
void Print() {std::cout << "Printing from Parent Class.\n"; };
};
class ChildClass : public ParentClass{
public:
void Print() {std::cout << "Printing from Child Class.\n"; };
};
void PrintFromParentAndChildClasses(ParentClass* ParentPointer) {
std::cout << "Printing from instances of parent and child classes.\n";
ParentPointer->Print();
ChildClass* ChildPointer = (ChildClass*)ParentPointer;
ChildPointer->Print();
};
int main() {
std::shared_ptr<ParentClass> ParentPointer = std::make_shared<ChildClass>();
PrintFromParentAndChildClasses(ParentPointer.get());
return 0;
}
Here’s the output of this program:
Printing from instances of parent and child classes.
Printing from Parent Class.
Printing from Child Class.
Link to the working example: https://onlinegdb.com/x87AqeySV
And it’s actually a good design pattern that’s used in a lot of frameworks, such as .NET, WPF, Java, Spring and Unreal Engine itself, as you’ve clearly seen yourself: in decorators, tasks and services of Behavior Trees you get a base Controller and Pawn classes and then you cast them down to their classes if you need and just use them.
Even Uncle Bob talks about things you can do with this design patterns and down casting.
No, as clearly shown in my example, you don’t need virtual functions and override them if you know which class you’re working with and you want to down cast, which is a valid way to program and doesn’t violate any of the principles.
While this works, it’s an anti-pattern. You’ve just created additional infrastructure, which doesn’t achieve anything: you have a more complex architecture of classes and you still need to downcast to the base class same as in solution with down casting to the specific class.
While the solution with inheritance will just might come in handy for a completely different problem*, in this case it’s useless and actually makes the solution worse from the design and architecture perspective. Plus it doesn’t solve the original problem: your answer was simply “rewrite your solution using inheritance network and it might work this time”, since, as explained above, there’s no problem with down casting to specific classes and using their members.
*Such as making base AI classes that can control basic actor behavior: basic movement, and then we’d create specific AI classes for different actors.