Почему частная виртуальная функция-член производного класса доступна из базового класса

Рассмотрим следующий фрагмент кода:

#include <iostream>
    
class Base
{
public:
  Base()
  {
      std::cout << "Base::constr" << std::endl;
      print();
  }
  virtual ~Base() = default;

  void print() const
  {
      printImpl();
  }
  
private:
    virtual void printImpl() const
    {
        std::cout << "Base::printImpl" << std::endl;
    }
};

class Derived : public Base
{
public:        
    Derived()
    {
        std::cout << "Derived::constr" << std::endl;
    }
    
private:
    void printImpl() const override
    {
        std::cout << "Derived::printImpl" << std::endl;
    }
};

int main()
{
    Base* ptr = new Derived();
    ptr->print();
    delete ptr;

    return 0;
}

Приведенный выше код напечатает следующее:

Base::constr

Base::printImpl

Derived::constr

Derived::printImpl

но я не понимаю, почему функция printImplчастный доступна из базовой print функции. В моем понимании this указатель, неявно переданный в print функцию, содержит адрес производного объекта, но я думал, что частные функции-члены могут быть вызваны ТОЛЬКО из функций-членов (и из friend функций) одного и того же класса, и здесь класс Base не тот же самый. класс как Derived, хотя есть отношение is a.

Внутри класса доступны приваты. Таким образом, функция базы print имеет доступ к базе printImpl. Преимущество такого типа идиома невиртуального интерфейса заключается в том, что он отделяет общедоступный API (print) от API, доступного для наследования (printImpl).

Eljay 10.04.2022 14:55
Получение данных из формы с помощью JavaScript - краткое руководство
Получение данных из формы с помощью JavaScript - краткое руководство
Получить данные из формы с помощью JS очень просто: вы запрашиваете элемент формы, передаете его конструктору new FormData() и, наконец, получаете...
Пользовательские правила валидации в Laravel
Пользовательские правила валидации в Laravel
Если вы хотите создать свое собственное правило валидации, Laravel предоставляет возможность сделать это. Создайте правило с помощью следующей...
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
1
1
55
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Частная функция базового класса недоступна в производном классе, но ее можно переопределить, поскольку это два разных понятия.

Для вызова print из конструктора класса Baseесть следующее правило:

In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Objects are constructed from the base up, “base before derived”.

P.S. В целом лучше избегать вызовов виртуальных функций из конструкторов или деструкторов.

Причина, по которой в коде вы напечатали Base::printImpl, заключается в том, что он вызывается из конструктора Base. Производный класс еще не создан, поэтому все вызовы виртуальных функций будут относиться к версиям Base.

После построения вызовы print всегда будут перенаправляться на версию производного класса. Отмечены ли функции private, public, protected, здесь не имеет значения.

Во-первых, как отмечает @Eljay — printImpl() — это метод, пусть и виртуальный, класса Base. Итак, он доступен из базового класса. Derived просто предоставляет другой реализация. И весь смысл виртуальных функций в том, что вы можете вызвать переопределение подкласса, используя ссылку или указатель базового класса.

Другими словами, private относится только к доступу по подклассам; бессмысленно сохранять что-то private из базового класса класса: если метод вообще известен базовому классу, это должен быть метод из базового класса... виртуальный метод.


Сказав все это — обратите внимание, что Derived версия printImpl() фактически является недоступный из print() — когда она вызывается в конструкторе базового класса. Это связано с тем, что во время этого вызова сконструированная виртуальная таблица является только таблицей Base, поэтому printImpl указывает на Base::printImpl.

I thought private member functions could be called ONLY from the member functions of the same class

И действительно, print() является членом Base, который вызывает printImpl() — еще один метод Base.

Мой вопрос был немного другим: я понимаю, что функция-член Base класса может оценивать частную print() функцию Base, но поскольку переопределенной функцией printImpl() является ЧАСТНЫЙ в классе printImpl, я думал, что компилятор скажет что-то вроде: "вы можете" t получить доступ к частной функции-члену, которая выходит за рамки класса Derived». Я предполагаю, что компилятор не «видит» фактический объект, на который указывает Derived, и из-за этого нет никакой ошибки компиляции.

David Hovsepyan 10.04.2022 16:54

Метод printImpl() всего один, даже если у него разные реализации. Я попытаюсь это немного прояснить.

einpoklum 10.04.2022 16:58
Ответ принят как подходящий

I don't understand why printImpl private function is accessible from the base's print function.

Из документация этого пионера:

When a non-static class member is used in any of the contexts where the this keyword is allowed (non-static member function bodies, member initializer lists, default member initializers), the implicit this-> is automatically added before the name, resulting in a member access expression (which, if the member is a virtual member function, results in a virtual function call).

Итак, когда вы написали:

ptr->print();  //this is equivalent to writing (*ptr).print();

Приведенное выше утверждение эквивалентно написанию:

(*ptr).print();

Это означает, что адрес объекта, на который указывает указатель ptr (который не что иное, как сам ptr) является неявно передано для параметра неявный this функции-члена print.

Теперь внутри функции-члена print оператор, содержащий вызов printImpl(), эквивалентен следующему:

this->printImpl();

согласно цитируемое заявление в начале моего ответа. И из того же цитируемого утверждения, поскольку член printImpl является виртуальная функция-член, выражение this->printImpl() приводит к вызову виртуальной функции, что означает фактический вызов функции производного класса printImpl.

Я думал, что компилятор скажет что-то вроде: «вы не можете получить доступ к частной функции-члену, которая находится за пределами класса Derived» (поскольку мы обращаемся к ней из класса Base, а функция — ЧАСТНЫЙ). Я предполагаю, что компилятор не «видит» фактический объект, на который указывает ptr, и из-за этого нет никакой ошибки компиляции. Я прав?

David Hovsepyan 10.04.2022 16:58

Последняя часть вашего ответа иногда верна. вызов this->printImpl() может вызывать реализацию базового класса, даже если создаваемый объект является производным объектом, как это действительно происходит в примере кода.

einpoklum 10.04.2022 17:05

@DavidHovsepyan Когда статический и динамический типы, через которые выполняется вызов, различаются (вызов выполняется через указатель или ссылку), а функция-член является виртуальной функцией-членом, тогда происходит динамическая привязка (что происходит во время выполнения, а не во время компиляции).

Anoop Rana 10.04.2022 18:13

Другие вопросы по теме