В чем разница между переопределением и перегрузкой метода в C++?

«В чем разница между переопределением и перегрузкой метода в C++?» — вопрос из категории ООП, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Это фундаментально разные концепции, часто вызывающие путаницу.

Переопределение (Overriding)

  • Цель: Изменить поведение уже существующего метода базового класса в производном классе.
  • Контекст: Работает только в иерархии наследования (производный класс переопределяет метод базового).
  • Требования:
    1. Метод в базовом классе должен быть virtual.
    2. Сигнатуры методов (имя, тип параметров, const-квалификаторы) должны полностью совпадать.
    3. Типы возвращаемых значений должны быть ковариантны или одинаковы.
  • Связывание: Динамическое (runtime). Какой метод вызовется, зависит от реального типа объекта.
  • Пример:
    
    class Logger {
    public:
    virtual void log(const std::string& message) { // Виртуальный метод
        std::cout << "Base: " << message << std::endl;
    }
    };

class FileLogger : public Logger { public: // Переопределение: та же сигнатура, что и у базового virtual метода. void log(const std::string& message) override { std::ofstream file("app.log"); file << "File: " << message << std::endl; } };


### Перегрузка (Overloading)
*   **Цель:** Предоставить **несколько версий** метода с одним именем, но разной логикой для разных типов входных данных.
*   **Контекст:** Работает **в пределах одной области видимости** (одного класса или пространства имён).
*   **Требования:** Методы должны иметь **разные сигнатуры** (разное количество или типы параметров). Возвращаемый тип может отличаться, но он **не** учитывается при разрешении перегрузки.
*   **Связывание:** **Статическое** (compile-time). Компилятор выбирает версию на основе типов аргументов в точке вызова.
*   **Пример:**
```cpp
class MathUtils {
public:
    // Перегрузка внутри одного класса:
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; } // Другая сигнатура
    int add(int a, int b, int c) { return a + b + c; } // И ещё одна
};

Ключевое отличие на практике: Переопределение меняет «что делает» метод для объектов производного типа в полиморфной цепочке. Перегрузка расширяет «какие аргументы» может принимать метод с данным именем.