Подтвердить что ты не робот

Доступ к функциям-членам в наследовании С++

Я просто запутался в крошечной программе по наследованию ниже:

#include<iostream>
using namespace std;

struct B {
    virtual int f() { return 1; }
}; // f is public in B

class D : public B { 
    int f() { return 2; }
}; // f is private in D

int main()
{
    D d;
    B& b = d;
    cout<<b.f()<<endl; // OK: B::f() is public, D::f() is invoked even though it private
    cout<<d.f()<<endl; // error: D::f() is private
}
  • Я не могу понять, почему D::f() является приватным, D является публичным унаследованным из B, поэтому открытая функция f в B
    также публично в D (я знаю, что без наследования, доступ по умолчанию является закрытым по умолчанию)
  • f - это виртуальная функция в B, поэтому, если мы вызываем b.f(), мы на самом деле вызываем D::f(), но, как упоминалось выше, почему D::f() может быть вызван, хотя он частный?

Можно ли подробно объяснить проблему простого наследования?

4b9b3361

Ответ 1

Это необходимо сделать, поскольку виртуальная диспетчеризация - это концепция времени выполнения. Класс B не заботится о том, какой класс расширяет его, и ему все равно, является ли он закрытым или общедоступным, потому что он не может знать.

Я не могу понять, почему D:: f() является приватным, D является общедоступным, унаследованным от B, поэтому публичная функция f в B также публично в D (я знаю, что без наследования, доступ к члену по умолчанию является закрытым)

D::f() является закрытым, потому что вы сделали его приватным. Это правило не влияет на наследование или виртуальную отправку.

f является виртуальной функцией в B, поэтому, если мы называем bf(), мы фактически вызываем D:: f(), но, как упоминалось выше, почему D:: f() может быть вызван, хотя он частный?

Потому что в действительности при вызове b.f() компилятор понятия не имеет, какую функцию на самом деле вызывать. Он просто вызовет функцию f(), а так как B::f является виртуальным, вызываемая функция будет выбрана во время выполнения. Программа выполнения не имеет информации о том, какая функция является частной или защищенной. Он знает только функции.

Если функция выбрана во время выполнения, компилятор не может знать во время компиляции, какую функцию вызывать, а спецификатор доступа не может быть известен. На самом деле, компилятор даже не попытается проверить, будет ли вызванная функция быть частной или нет. Спецификатор доступа может быть в некотором коде, который компилятор еще не видел.

Как вы уже поняли, вы не можете напрямую вызвать D::f. Это именно то, что будет делать частный человек: запрет прямого доступа к члену. Вы можете, однако, получить доступ к нему косвенно через указатель или ссылку. Виртуальная отправка, которую вы используете, сделает это внутри.

Ответ 2

Спецификаторы доступа относятся только к имени функции, они не являются некоторыми ограничениями на то, как и когда функция может быть вызвана другими способами. Частная функция может вызываться вне класса, если она становится доступной некоторыми средствами, отличными от ее имени (например, указателем функции).

Для класса, объявленного с ключевым словом class, спецификатор доступа по умолчанию - private. Ваш код будет таким же, как:

// ...
class D: public B
{
private:
  int f() { return 2; }
};

Как вы можете видеть, f является закрытым в D. Не имеет значения, что спецификатор доступа имеет любую функцию в B с тем же именем. Подумайте, что B::f() и D::f() - две разные функции.

Эффект ключевого слова virtual заключается в том, что если f() без квалификатора области видимости вызывается в B ссылке, которая ссылается на объект D, то даже если он разрешает B::f(), фактически < Вместо этого вызывается t27 > .

Этот процесс по-прежнему использует спецификатор доступа для B::f(): доступ проверяется во время компиляции; но это может быть вопрос времени выполнения, для которого вызывается функция.

Ответ 3

Стандарт С++ имеет точный пример:

11.5 Доступ к виртуальным функциям [class.access.virt]

1 Правила доступа (раздел 11) для виртуальной функции определяются ее декларации и не затрагиваются правилами для функции, которая позже переопределяет его. [Пример:

class B { 
public:
  virtual int f();
};

class D : public B { 
private: 
  int f(); 
}; 

void f() { 
  D d; 
  B* pb = &d; 
  D* pd = &d; 
  pb->f();     // OK: B::f() is public, 
               // D::f() is invoked
  pd->f();     // error: D::f() is private
} 

- конец примера]

Не могу объяснить это более ясно.

Ответ 4

Приведенный ответ иллюстрирует, что делается, но зачем вам это делать, когда базовый класс вызывает private виртуальные функции?

Ну, есть шаблон дизайна, который называется шаблоном шаблона , который использует этот метод создания базового класса, который вызывает частные виртуальные функции в производном классе.

struct B 
{
    virtual ~B() {};
    int do_some_algorithm()
    {
       do_step_1();
       do_step_2();
       do_step_3();
    }

    private:
          virtual void do_step_1() {}
          virtual void do_step_2() {}
          virtual void do_step_3() {}
};

class D : public B 
{ 
   void do_step_1() 
   {
      // custom implementation
   }
   void do_step_2() 
   {
      // custom implementation
   }

   void do_step_3() 
   {
      // custom implementation
   }
};

int main()
{
   D dInstance;
   B * pB = &dInstance;
   pB->do_some_algorithm();
}

Это позволяет нам не подвергать пользовательские шаги класса D интерфейсу public, но в то же время позволяет B вызывать эти функции с помощью функции public.

Ответ 5

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

Сама функция не private; его имя.

Следовательно, эта функция не может быть названа вне области действия класса, например. от main. Однако вы все равно можете сделать это с помощью имени public (т.е. Переопределенной базовой виртуальной функции) или из области, в которой доступно имя функции, несмотря на квалификатор private (например, функция-член этого класса).

Вот как это работает.

Ответ 6

почему D:: f() может быть вызван, хотя он частный?

Чтобы понять механизм виртуальных функций, хорошо знать, как это обычно реализуется. Функция во время выполнения фактически является не более чем адресом в памяти, где находит исполняемый код тела функции. Чтобы вызвать функцию, нам нужно знать ее адрес (указатель). Объект С++ с представлением виртуальных функций в памяти содержит так называемый vtable - массив указателей на виртуальные функции.

типичная реализация vtable

Ключевым моментом является то, что в производных классах vtable повторяет (и может расширять) vtable базового класса, но если виртуальная функция переопределяется, ее указатель заменяется на производный объект vtable.

Когда вызов виртуальной функции выполняется с помощью указателя базового класса, адрес виртуальной функции вычисляется как смещение в массиве vtable. Никаких других проверок не выполняется, просто выполняется адрес функции. Если это объект базового класса, он будет адресом функции базового класса. Если это производный объект класса, он будет адресом функции производного класса, не имеет значения, было ли оно объявлено закрытым или нет.

Как это работает.

Ответ 7

Член struct по умолчанию является общедоступным, а член class по умолчанию является закрытым. Таким образом, f() в B является общедоступным, и когда он получен для D, потому что вы явно не объявляете его общедоступным, поэтому в соответствии с правилами вывода он становится частным.