Вопрос по inheritance, c++, virtual-functions – Открытая виртуальная функция, производная от C ++

18

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

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void func() {
        cout<<"A::func called"<<endl;
    }
    private:
};
class B:public A
{
    public:
    B()
    {
        cout<<"B constructor called"<<endl;
    }
    private:
    void func() {
        cout<<"B::func called"<<endl;
    }
};
int main()
{
    A *a = new B();
    a->func();
    return 0;
}

Удивительно (для меня) результат был:

B constructor called
B::func called

Разве это не нарушает частный доступ, установленный для этой функции. Это ожидаемое поведение? Это стандартный обходной путь или лазейка? Обходятся ли уровни доступа при разрешении вызовов функций через VTABLE?

Любое понимание этого поведения было бы очень полезно.

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

class C: public B
{
    public:
    void func() {
        cout<<"C::func called"<<endl;
    }
};

и основная программа испытаний для:

int main()
{
    A *a = new C();
    a->func();
    return 0;
}

вывод:

C::func called
Ну ... скажем, что C ++ позволяет вам повеситься, если вы одержимы этим. Naveen
Я просто хотел указать на этоcannot сделать это в Java. Когда метод переопределяется, он должен иметь уровень доступа, равный или более открытый, чем метод, который переопределяется. Причина состоит в том, что вы должны гарантировать, что версия метода подкласса может быть вызвана из всех контекстов, из которых может быть вызвана версия суперкласса. Я нахожу это интересным, это разрешено в C ++. Tom

Ваш Ответ

3   ответа
3

Ну ты звонишьA::func() которыйpublic хотя вB объект переопределяетсяB::func(), Это общая схема со следующими последствиями:

func is not intended to be called on derived B objects

func cannot be overridden in classes derived from B

@Tom: вызов A * a = new B () работает, потому что вы вызываете A :: func (). Вызов B * b = new B () не компилируется, поскольку вы вызываете private B :: func () вне B.
@Tom: C ++ и интуиция не всегда идут рука об руку.
Если вы говорите, что «func не предназначен для вызова производных объектов B», то почему он работает?
... вне Б или его друзей, то есть.
@laalto: я понимаю это. Но кажется, что Эшвин говорит, что -> func вызывает B :: func. Кажется, это нарушает уровень доступа, потому что всякий раз, когда я хочу вызвать закрытый B :: func, я могу просто привести B к A, а затем вызвать func (). Кажется, я не смогу этого сделать. Похоже, вы говорите, что цель изменения уровня доступа на частный в подклассе состоит в том, чтобы подклассы не могли переопределить func () И так, что если у вас есть что-то статически типа B, вы не можете вызвать func ( ). Но метод все еще не является действительно закрытым, если вы можете обойти его путем приведения :-(.
11

Поведение правильное. Всякий раз, когда вы объявляете свою функцию как «виртуальную», вы указываете компилятору генерировать виртуальный вызов вместо прямого вызова этой функции. Всякий раз, когда вы переопределяете виртуальную функцию в классе-потомке, вы указываете поведение этой функции (вы не изменяете режим доступа для тех клиентов, которые полагаются на интерфейс & quot; родительского & quot; s & quot;).

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

Рассмотрим пример:

void process(const A* object) {
   object->func();
}

& Quot; процесс & Quot; функция зависит от родительского интерфейса. Предполагается, что он будет работать для любого класса, открытого от A. Вы не можете получить открытый ключ B от A (говоря, что «каждый B есть A»), но скрыть часть его интерфейса. Те, кто ожидают «А» должен получить полностью функциональную "A".

Спасибо вам всем. Тем не менее, есть один вопрос по этому вопросу, который беспокоит меня. Я могу иметь указатель на объект Child и вызывать его закрытый метод просто, используя static_cast для его родительского объекта ... Ну, я полагаю, что в этом случае static_cast означает "полагаться на родительский интерфейс" ...
Я мог бы добавить, что требование компилятора запретить вызов частной виртуальной функции B через a-gt; func почти требовало бы невозможного. Нарушение прав доступа проверяется во время компиляции, но этот доступ происходит во время выполнения. И даже если бы вы заполняли исполняемый файл проверками времени выполнения, действительно ли было бы желательно, чтобыuser программы видит, что программа останавливается с ошибкой нарушения прав доступа?
Спасибо, СадСидо. Вы, кажется, единственный, кто обратился к "общедоступной". Я действительно забыл, что существует не только публичное наследование, потому что я использую только публичное наследование. Это характер публичного наследования, которое допускает такое поведение - что имеет больше смысла. Я думал, что это лучшее объяснение. Спасибо.
Согласен. Только тот компилятор, преобразовывающий функцию Private в public, казался недопустимым. Но это правда "Вы не можете публично получить B из A (говоря, что" каждый B есть A "), но скрыть часть его интерфейса. Те, кто ожидают «А» должен получить полностью функциональную «А». Спасибо за объяснение.
Добавим к этому - рассмотрим альтернативу. Если интерфейс базового класса изменяется производным классом, функции, которые полагаются на интерфейсы, могут аварийно завершить работу. Например, скажемvoid doFunc (A* ptr) { ptr->func(); } был написан, когда был написан. Затем B расширяет A и модифицирует его интерфейс. посколькуB* ЭТОA*, компилятор позволяет нам передавать doFunc (& amp; bObj). Это может привести к сбою, если B будет позволено изменить режим доступа func (). По сути, программирование с интерфейсами является причиной, по которой это происходит.
12

a былиB* это не скомпилируется. Причина в том, что доступ к элементу разрешается компилятором статически, а не динамически во время выполнения. Во многих книгах по С ++ рекомендуется избегать такого кодирования, потому что это сбивает с толку менее опытных программистов.

Говоря «избегайте такого кодирования, потому что это сбивает с толку менее опытных программистов» подразумевает, что есть причины, по которым вы могли бы захотеть сделать это, если бы это было не для «менее опытных программистов». Здесь?
@ Навин: Спасибо. Наверное, я забыл, что компилятор не хранит эту информацию о доступе во время выполнения ... для меня это выглядит как нежелательное поведение. Тем более, что вы можете просто привести B к A, чтобы попытаться получить доступ к закрытой функции. Теперь это имеет смысл ... Мне просто это не нравится :-).
@Laurence: я помню, что читал кое-что о том, почему кто-то хотел бы сделать это, и предложения, чтобы избежать этого С тех пор я забыл причину, почему это может быть полезно.
Что вы имеете в виду, что доступ к члену разрешается статически компилятором? Весь смысл vtable заключается в том, что компилятор не знает, какую функцию вызывать во время компиляции ... поэтому он должен искать ее во время выполнения. Вы можете уточнить?
@Tom: во время компиляции проверяется семантика вашей программы. Вы вызываете func (), используя A *, который является допустимым вызовом. Компилятор не может знать, что этот вызов разрешается вызовом частной функции-члена во время выполнения. Во время выполнения нет понятия приватного или публичного, это просто вызов функции. Если бы вы использовали B * вместо A *, то вызов func () был бы недействительным, и компилятор пометил бы его как ошибку.

Похожие вопросы