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

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

#include<iostream>

using namespace std;
class base
{
public:
    virtual void add() {
        cout << "hi";
    }
};

class derived : public base
{
private:
    void add() {
        cout << "bye";
    }
};

int main()
{
    base *ptr;
    ptr = new derived;
    ptr->add();
    return 0;
}

Вывод bye

У меня нет проблем с тем, как это реализовано. Я понимаю, что вы используете vtables, а vtable производного содержит адрес новой функции add(). Но add() является частным, если компилятор не генерирует ошибку при попытке получить к ней доступ за пределами класса? Почему-то это не кажется правильным.

4b9b3361

Ответ 1

add() является только закрытым в derived, но у вас есть статический тип base* - поэтому применяются ограничения доступа base.
В общем, во время компиляции вы даже не можете узнать, какой будет динамический тип указателя на base, он может, например, изменение на основе ввода пользователем.

Это для С++ 03 §11.6:

Правила доступа (статья 11) для виртуальной функции определяются ее объявлением и не зависят от правил для функции, которая впоследствии отменяет ее.
[...] Доступ проверяется в точке вызова с использованием типа выражения, используемого для обозначения объекта, для которого функция-член называется [...]. Доступ к функции-члену в классе, в котором он был определен [...], вообще не известен.

Ответ 2

Модификаторы доступа, такие как public, private и protected, применяются только во время компиляции. Когда вы вызываете функцию через указатель на базовый класс, компилятор не знает, что указатель указывает на экземпляр производного класса. Согласно правилам, которые компилятор может вывести из этого выражения, этот вызов действителен.

Обычно семантическая ошибка уменьшает видимость члена в производном классе. Современные языки программирования, такие как Java и С#, отказываются компилировать такой код, поскольку элемент, который видим в базовом классе, всегда доступен в производном классе с помощью базового указателя.

Ответ 3

Чтобы немного добавить к Георгу ответ:

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

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