Почему разрешено вызывать закрытый виртуальный метод производного класса через указатель базового класса? - PullRequest
13 голосов
/ 14 февраля 2011
# include <iostream>
using namespace std;

class A
{
    public:
    virtual void f()
    {
        cout << "A::f()" << endl;
    }
};
class B:public A
{
    private:
    virtual void f()
    {
        cout << "B::f()" << endl;
    }
};
int main()
{
    A *ptr = new B;
    ptr->f();
    return 0;
}

Этот код работает правильно и печатает B :: f ().Я знаю, как это работает, но почему этот код разрешен?

Ответы [ 6 ]

14 голосов
/ 14 февраля 2011

Контроль доступа выполняется во время компиляции, а не во время выполнения. В общем случае для вызова f() нет способа узнать тип времени выполнения объекта, на который указывает ptr, поэтому нет проверки на спецификаторы доступа производного класса. Вот почему звонок разрешен.

Что касается того, почему классу B вообще разрешено переопределять с помощью закрытой функции - я не уверен. Конечно, B нарушает интерфейс, подразумеваемый его наследованием от A, но в целом язык C ++ не всегда обеспечивает наследование интерфейса, так что тот факт, что это просто неправильно, не означает, что C ++ остановит вас.

Так что я бы предположил, что, вероятно, есть некоторый вариант использования для этого класса B - подстановка все еще работает с динамическим полиморфизмом, но статически B не заменяет A (например, могут быть шаблоны, которые вызывают f, которые будут работать с А в качестве аргумента, но не с В в качестве аргумента). Могут быть ситуации, когда это именно то, что вы хотите. Конечно, это может быть непреднамеренным следствием какого-то другого соображения.

2 голосов
/ 14 февраля 2011

Этот код разрешен, потому что f является общедоступным в интерфейсе А. Производный класс не может изменить интерфейс базового класса. (Переопределение виртуального метода не меняет интерфейс и не скрывает членов базы, хотя могут казаться, что оба они делают это.) Если производный класс может изменить интерфейс базы, он нарушит "is" отношения .

Если разработчики A хотят сделать f недоступным, то он должен быть помечен как защищенный или закрытый.

0 голосов
/ 10 октября 2012

Проверка контроля доступа к функциям происходит на более поздней стадии вызова функции c ++. Порядок на высоком уровне будет подобен поиску имени, выводу аргумента шаблона (если есть), разрешению перегрузки, а затем проверке контроля доступа (public / protect / private).

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

A *ptr = new B;
ptr->f();

Но все вышеперечисленное происходит во время компиляции, поэтому они действительно статичны. Хотя вызов виртуальной функции, часто приводимый в действие vtable & vpointer, представляет собой динамический процесс, который происходит во время выполнения, поэтому вызов виртуальной функции ортогональн к управлению доступом (вызов виртуальной функции происходит после контроля доступа), поэтому вызов функции f () фактически завершился B :: f () независимо от того, является ли управление доступом частным.

Но если вы попытаетесь использовать

B* ptr = new B;
ptr->f()

Это не пройдет, несмотря на vpointer & vtable, компилятор не позволит ему скомпилироваться во время компиляции.

Но если вы попробуете:

B* ptr = new B;
((static_cast<A*>(ptr))->f();

Это бы отлично работало.

0 голосов
/ 14 февраля 2011

В дополнение к ответу Стива:

  • B публично получен из A. Это подразумевает подстановку Лискова
  • Переопределение f для приватности, кажется, нарушает этот принцип, но на самом деле это так.не обязательно - вы все равно можете использовать B как A без мешающего кода, поэтому, если частная реализация f все еще в порядке для B, никаких проблем
  • Возможно, вы захотите использовать этот шаблон, если B долженбыть заменяемым Лисковым для A, но B также является корнем другой иерархии, которая на самом деле не связана (в замене Лискова) с A, где f больше не является частью открытого интерфейса.Другими словами, класс C, производный от B, используемый через указатель на B, скрывал бы f.
  • Однако это на самом деле весьма маловероятно, и, вероятно, было бы лучше получить производную Bот частного или защищенного
0 голосов
/ 14 февраля 2011

Как и в Java, в C ++ вы можете увеличивать видимость методов, но не уменьшать ее.

0 голосов
/ 14 февраля 2011

Ваш базовый класс определяет интерфейс для всех унаследованных потомков.Я не понимаю, почему это должно препятствовать упомянутому доступу.Вы можете попробовать извлечь класс из 'B' и использовать базовый интерфейс для вызова, что приведет к ошибке.

Cheers!

...