Можно ли вызвать метод базового класса из производного класса?

Ответ

Да, и это стандартная практика. Для явного вызова метода базового класса используется оператор разрешения области видимости BaseClass::.

Зачем это нужно?

  1. Расширение функциональности: Вы можете добавить новое поведение в переопределенном методе, вызвав сначала базовую реализацию.
  2. Обход виртуального механизма: Даже для виртуальных методов вызов через Base:: будет статическим (не виртуальным) и обратится именно к реализации в базовом классе.
  3. Решение проблемы «сокрытия имени»: Если метод в производном классе перегружает, а не переопределяет метод базового класса, вы все равно можете получить доступ к скрытой базовой версии.

Примеры:

#include <iostream>

class Base {
public:
    virtual void log(const std::string& message) {
        std::cout << "[Base Log]: " << message << 'n';
    }
    void utility() { std::cout << "Base utilityn"; }
};

class Derived : public Base {
public:
    // 1. Переопределение виртуального метода с вызовом базовой версии.
    void log(const std::string& message) override {
        // Сначала выполняем базовую логику.
        Base::log(message);
        // Затем добавляем свою.
        std::cout << "[Derived Addendum] Timestamp added.n";
    }

    // 2. Сокрытие невиртуального метода.
    void utility(int x) { // Это НЕ переопределение, а новая перегрузка.
        std::cout << "Derived utility with int: " << x << 'n';
    }
    // Чтобы использовать базовую версию без параметров, можно добавить using или явный вызов.
    void callBaseUtility() {
        Base::utility(); // Явный вызов метода базового класса.
    }
};

int main() {
    Derived d;
    d.log("Test Message");
    // Вывод:
    // [Base Log]: Test Message
    // [Derived Addendum] Timestamp added.

    // d.utility(); // Ошибка компиляции: utility() скрыт.
    d.callBaseUtility(); // Вывод: Base utility

    // Даже через указатель на базовый класс можно вызвать базовую реализацию?
    Base* bPtr = &d;
    // bPtr->Base::log("Direct"); // Так нельзя, синтаксис Base:: только внутри класса.
}

Важно: Вызов Base::method работает только из контекста производного класса (в его методах). Извне через указатель на базовый класс так сделать нельзя.

Ответ 18+ 🔞

Ну ты представляешь, эта штука с BaseClass:: — это как взять у бати старый, проверенный молоток, чтобы гвоздь забить, но перед этим ты его в лазерный прицел превратил, блядь. Стандартная практика, да, но какая же она хитрая, ёпта.

А нахуя это вообще?

  1. Расширить, а не сломать. Ты можешь в своём методе сначала крикнуть: «Батя, сделай своё дело!», а потом уже от себя припизднуть что-то новенькое. Порядок, уважение к предкам, никакого распиздяйства.
  2. Обмануть систему. Даже если метод виртуальный и все бегают как угорелые, ты можешь ткнуть пальцем и сказать: «А вот эту конкретную реализацию из базового класса давай сюда, я с ней работать буду». Статический вызов, никаких тебе полиморфных плясок, чётко и по делу.
  3. Достать из-под сукна. Если в твоём классе объявилась своя функция с таким же именем, но другими параметрами, то родная батина версия — шёл бы ты на хуй — и скрывается. А через Base:: ты её как из-под земли достанешь, будто она там и не пряталась.

Смотри, как это выглядит в деле:

#include <iostream>

class Base {
public:
    virtual void log(const std::string& message) {
        std::cout << "[Base Log]: " << message << 'n';
    }
    void utility() { std::cout << "Base utilityn"; }
};

class Derived : public Base {
public:
    // 1. Переписываем метод, но батин кусок оставляем.
    void log(const std::string& message) override {
        // Сперва делаем как раньше, по-старому.
        Base::log(message); // Вот он, волшебный пендель системе.
        // А теперь от себя, для красоты.
        std::cout << "[Derived Addendum] Timestamp added.n";
    }

    // 2. А вот тут начинается пиздец сокрытия.
    void utility(int x) { // Это новая функция, просто имя совпало!
        std::cout << "Derived utility with int: " << x << 'n';
    }
    // И чтобы старую utility() без параметров вызвать, надо либо using, либо вот так, в лоб:
    void callBaseUtility() {
        Base::utility(); // Явно говорим: дай мне ту, старую, из Base.
    }
};

int main() {
    Derived d;
    d.log("Test Message");
    // На экране будет:
    // [Base Log]: Test Message  <-- это батя поработал
    // [Derived Addendum] Timestamp added. <-- а это наш апгрейд

    // d.utility(); // Ошибка, компилятор охуел: "Чё? Какой utility()? Я знаю только с int!"
    d.callBaseUtility(); // А вот так — пожалуйста. Выведет: Base utility

    // И да, запомни: снаружи, через указатель на Base, такую магию не провернёшь.
    // Base* bPtr = &d;
    // bPtr->Base::log("Direct"); // Это хуйня, так не работает. Base:: — это внутренняя кухня класса.
}

Короче, инструмент мощный. Прям ядрёна вошь. Позволяет не быть полупидором, который всё с нуля переписывает, а грамотно надстраивать старое. Главное — понимать, где эта хитрая жопа с сокрытием имени вылезет, чтобы не охуеть в самый неподходящий момент.