Использование-декларация работает неправильно
В следующем примере я пытаюсь скрыть 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, как вы могли ожидать = > ссылка
(*) За исключением того факта, что изменение правила доступа к виртуальному методу по иерархиям является плохим дизайном для моего разума = > проверить это.