Использование-декларация работает неправильно

В следующем примере я пытаюсь скрыть using Employee::showEveryDept из последнего дочернего класса Designer, сделав его закрытым в классе Elayer -

#include <iostream>

class Employee {
private:
    char name[5] = "abcd";
    void allDept() { std::cout << "Woo"; }

public:
    void tellName() { std::cout << name << "\n"; }
    virtual void showEveryDept()
    {
        std::cout << "Employee can see every dept\n";
        allDept();
    }
};

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

class Designer : public ELayer {
private:
    char color = 'r';

public:
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; }
};

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

Но он все еще компилируется, а выход -

Employee can see every dept
Woo
Designer can see own dept

Но я явно сделал это частным, см. private: using Employee::showEveryDept;

Что я здесь делаю неправильно?

Ответы

Ответ 1

Имена членов класса обладают следующими свойствами:

  • Имя - неквалифицированный идентификатор.
  • Декларативная область - класс, в котором было объявлено имя.
  • Доступ - права имени в этом регионе.

Это относится к самим именам - не к какой-либо переменной или функции, к которой относится имя. Можно иметь одну и ту же функцию или переменную с именем с тем же именем, но в другой декларативной области.

Когда класс наследуется, декларативная область производного класса включает все имена из базового класса; но доступ может быть изменен в зависимости от типа наследования: хотя возможно только объявить член как public, protected или private, после наследования вы можете получить член, не имеющий доступа.

Вот таблица доступности имен и регионов в вашем коде:

Доступность имен

Обратите внимание, что tellName является общедоступным во всех трех классах, несмотря на то, что он не был переопределен в Designer. Соответственно, ELayer using Employee::tellName; является избыточным, так как tellName был бы public в ELayer в любом случае.

Эффект ELayer using Employee::showEveryDept; заключается в том, что showEveryDept доступ в ELayer равен private.


Поиск имени - это процесс определения того, какая комбинация имени и области найдена путем вызова имени. Контекст этого вызова включает в себя:

  • Сайт вызова, то есть область применения имени
  • Любая явно указанная область в вызове (например, Foo::name)
  • Выражение, обозначающее объект, чей член обращается (например, (*E))

Контроль доступа также учитывает:

  • Связь между вызывающим контекстом и декларативной областью, в которой было найдено имя.

Например, поиск showEveryDept в контексте ELayer найдет комбинацию ELayer::showEveryDept с доступом private.

Но поиск одного и того же имени в контексте Employee найдет комбинацию Employee::showEveryDept, которая имеет доступ public.

Такое поведение одинаково независимо от того, относятся ли эти две комбинации к одной и той же функции.

Без воспроизведения полного списка правил о том, как преобразуется контекст вызова, к которому выполняется поиск декларативных регионов, использование:

`E->showEveryDept`

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

Последним шагом проверки доступа является сравнение public и Employee с сайтом вызова, который равен main(). Правило заключается в том, что public предоставляет доступ ко всем сайтам, поэтому проверка доступа проходит.


virtual-ness не зависит от свойств имен, ни от области, в которой это имя просматривается. В отличие от доступа, быть виртуальным является свойством функции, а не комбинациями имен.

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

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

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

Итак, в вашем коде E->showEveryDept активирует виртуальную отправку. Проверка доступа проходит, как описано выше, а затем виртуальная диспетчеризация вызывает конечный переопределитель, который, как представляется, является телом, определенным в Employee в этом примере.

В вашем фактическом примере virtual является спорным, так как функция не переопределяется. Но даже если вы переопределили showEveryDept как частную функцию в ELayer (вместо объявления using), она все равно вызовет это тело функции.

Ответ 2

Вы думаете об этом неправильно.

С++ имеет концепцию Name Lookup, это хорошо построенная концепция, которая не часто приходит нам на ум, но это происходит везде a name. Выполняя:

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

Строка E->showEveryDept() выполняет поиск неквалифицированного имени для члена (в этом случае функция-член), принадлежащая классу E. Поскольку это доступное имя, программа является законной.


Мы также знаем, что Designer было получено из ELayer, где showEveryDept() объявлено private, как вы здесь:

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

Но вы просто сделали явно ввести название showEveryDept() из Employee class в ELayer; введенный в доступ к private. Значит, мы не можем напрямую получить доступ к этому имени (или вызвать эту функцию), связанному с ELayer вне член класса/статических функций.

ELayer* e = new Designer();
e->showEveryDept();    //Error, the name showEveryDept is private within ELayer

Однако, поскольку showEveryDept() имеет доступ к public в базовом классе ELayer, Employer; к нему все еще можно получить доступ, используя квалифицированный поиск имени

ELayer* e = new Designer();
e->Employer::showEveryDept();    //Ok, qualified name lookup, showEveryDept is public

Имена из ELayer, доступные в Designer, будут определяться его спецификацией доступа. Как вы можете видеть, имя showEveryDept() является закрытым, поэтому Designer не может даже использовать такое имя.

Для вашей текущей иерархии классов и определений это означает, что, учитывая:

Employee* E = new Designer();
ELayer*   L = new Designer();
Designer* D = new Designer();

- для неквалифицированный поиск:

  • Это работает

    E->showEveryDept();                // works!
    
  • Это не удается:

    L->showEveryDept();                // fails; its private in Elayer
    
  • Это также не удается:

    D->showEveryDept();                // fails; its inaccessible in Designer
    

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

  • Это не удается:

    D->Elayer::showEveryDept();        // fails! its private in Elayer
    
  • Это работает:

    D->Employee::showEveryDept();      // works! its accessible in Employee
    
  • Это также работает:

    L->Employee::showEveryDept();      // works! its accessible in Employee
    

Обратите внимание, что: ввод имени любой функции-члена, B::func (например) из базового класса B, в производный класс D, не переопределяет B::func, он просто делает B::func видимый для разрешения перегрузки в контексте производного класса. См. Ответы на этот вопрос и this

Ответ 3

Утверждение

E->showEveryDept();

обращается к showEveryDept в тип, известный во время компиляции для *E. Это Employee, где этот член доступен.

Ответ 4

В вашей функции main() будьте ясны относительно вашего типа и назовите его так:

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // will work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
    D->showEveryDept();   // <-- Not legal now
}

Это приведет к ошибке -

prog.cc: In function 'int main()':
prog.cc:28:22: error: 'virtual void Employee::showEveryDept()' is inaccessible within this context
     D->showEveryDept();
                      ^
prog.cc:8:26: note: declared here
             virtual void showEveryDept(){std::cout<< "Employee can see every dept\n";

Ответ 5

Что я здесь делаю неправильно?

Вы ничего не делаете неправильно. *

Является ли ожидаемый результат неправильным:

Спецификатор доступа виртуальной функции проверяется на тип Employee, а не на Designer, как вы могли ожидать = > ссылка

(*) За исключением того факта, что изменение правила доступа к виртуальному методу по иерархиям является плохим дизайном для моего разума = > проверить это.