Несколько слов о перегрузке при наследовании
Напомню:
- Перегрузка - когда мы определяем метод с тем же именем, но другой сигнатурой (другие параметры)
- Переопределение - определяем метод с тем же именем и сигнатурой что и в родительском классе
Пример перегрузки:
class Parent
{
public:
    void print() { std::cout << "Parent\n"; }
};
class Child : public Parent
{
public:
    using Parent::print;
    void print(int x) { std::cout << "Child\n"; }
};
Стоит обратить внимание, что для перегрузки обязательна строка using Parent::print;, в ином случае дочерний метод просто перекроет родительский (Соответственно дочерний экземпляр будет способен вызвать только print(int x), а print() - нет.
Попробуем по банальному переопределить метод (без использования виртуальных методов):
class Parent
{
public:
    void print() { std::cout << "Parent\n"; }
};
class Child : public Parent
{
public:
    void print() { std::cout << "Child\n"; }
};
Получается мы просто перекрыли дочерним методом родительский.
int main()
{
    Parent * p = new Parent();
    p->print(); // Parent
    Child * c = new Child();
    c->print(); // Child
    Parent * pc = new Child();
    pc->print(); // Parent
    delete p;
    delete c;
    delete pc;
    return 0;
}
Если в первых двух случаях все понятно: экземпляры вызывают соответствующие их классам методы, то с третим не все так очевидно. Мы создали указатель на родительский класс, однако он указывает на дочерний экземпляр (Если точнее, то этот указатель указывает только на Parent часть дочернего объекта и вызывать чисто дочерние методы через этот указатель не выйдет, только родительские). При этом вызываться будет все таки Parent::print().
Соответственно, решить эту проблему можно при помощи виртуальных методов - особый тип методов, который при вызове выполнит наиболее дочерний метод, который существует.
class Parent
{
public:
    virtual void print() { std::cout << "Parent\n"; }
};
class Child : public Parent
{
public:
    virtual void print() { std::cout << "Child\n"; }
};
int main()
{
    Parent * p = new Parent();
    p->print(); // Parent
    Child * c = new Child();
    c->print(); // Child
    Parent * pc = new Child();
    pc->print(); // Child
    delete p;
    delete c;
    delete pc;
    return 0;
}
Теперь все так же вызывается Parent::print(), но так как он virtual, то компилятор понимает, что нужно проверить есть ли переопределения этого метода в дочернем классе, соответственно он найдёт Child::print().
