Ответ 1
Короткий ответ: не существует поведения undefined. Поведение, которое вы видите:
- Выражение
&A::a
представляет собой попытку получить указатель на элемент, указывающий на элемент a класса A. Если a защищен в A, это выражение проходит только проверки доступа в классе A (или друзья A). В классе B, полученном из A, вы можете получить тот же указатель на член только через выражение&B::a
(обратите внимание, что тип этого выражения будетint A::*
). Так:- если
A::a
защищен в A, выражение&A::a
не допускается в функции-члене производного класса B. Это ваша ошибка компилятора. - Если
A::a
является общедоступным в A, это выражение действительно, создавая указатель на memeber.
- если
- Поток указателя на элемент в
ostream
, например, с помощьюcout << &A::a
будет печатать1
. Это вызвано вызовомostream::operator << (bool)
. Вы можете использовать манипулятор boolalpha, чтобы убедиться, что это действительно выбранная перегрузка:cout << boolalpha << &A::a
будет печататьtrue
. - Если вы используете модифицированное выражение & (A:: a) или просто & a, никакой указатель на элемент не формируется. Здесь берется адрес члена a текущего объекта (то есть, как
&(this->a)
), который является регулярным указателем на int. Этот доступ к защищенному члену субобъекта базового класса*this
действителен, поэтому этот вариант можно использовать, даже если a защищен в A.
Более подробное объяснение:
В стандарте говорится (5.3.1/3):
Результат унарного и оператора - указатель на его операнд. операнд должен быть lvalue или квалифицированным. Если операнд является квалифицированный идентификатор, обозначающий нестатический член m некоторого класса C с типом T, результат имеет тип "указатель на член класса C типа T" и является prvalue, обозначающее C:: m. [...]
Таким образом, выражение &A::a
пытается получить указатель-член для члена a класса A.
В следующем параграфе (5.3.1/4) уточняется, что только синтаксис & X:: m создает указатель на элемент - ни &(X::m)
, ни &m
, либо plain X::m
do:
Указатель на член формируется только тогда, когда явный и используется и его операнд - это квалифицированный идентификатор, не заключенный в круглые скобки.
Но такое выражение действует только в том случае, если доступ разрешен. В случае защищенного элемента (11.4/1) применяется:
Дополнительная проверка доступа за пределами описанных выше в пункте 11 применяется, когда нестатический элемент данных или нестатическая функция-член является защищенным членом его класса именования (11.2). Как описано ранее доступ к защищенному члену предоставляется, поскольку ссылка происходит в другом или члене некоторого класса C. Если доступ должен формироваться указатель на член (5.3.1), спецификатор вложенного имени должен обозначать C или класс, полученный из C. [...]
В вашем случае доступ к защищенному члену a будет предоставлен, поскольку ссылка на a встречается в члене класса B, полученном из A. Когда выражение пытается сформировать указатель на член, спецификатор вложенного имени (часть перед финальным ":: a" ) должна обозначать B. Таким образом, простейшая разрешенная форма &B::a
. Форма &A::a
разрешена только членам или друзьям самого класса A.
Нет форматированного оператора вывода для указателей на элемент (ни как istream, ни как функция свободного оператора), поэтому компилятор будет смотреть на перегрузки, которые можно вызвать с помощью стандартного преобразования (последовательности). Единственное стандартное преобразование из указателей в член в нечто другое описано в 4.12/1:
Значение знака [...] указателя на тип члена может быть преобразовано в prvalue типа bool. Значение [...] null указывает значение указателя к ложному; любое другое значение преобразуется в значение true. [...]
Это преобразование можно использовать без дополнительных преобразований для вызова basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n)
. Другие перегрузки требуют более длинных последовательностей преобразования, поэтому наилучшее совпадение имеет перегрузка.
Поскольку &A::a
принимает адрес некоторого члена, это не значение указателя на null. Таким образом, он преобразуется в true
, который печатается как "1" (noboolalpha) или "true" (boolalpha).
Наконец, выражение &(A::a)
допустимо в члене B, даже если a защищено в A. указанными выше правилами, это выражение не образует указатель на элемент, поэтому правило специального доступа, указанное выше, не применяется, В таких случаях 11.4/1 продолжается:
Все другие обращения включают (возможно неявное) выражение объекта (5.2.5). В этом случае класс выражения объекта должен быть C или класс, полученный из C.
Здесь впечатление объекта неявное (*this)
, т.е. A::a
означает то же, что и (*this).A::a
. Тип (*this)
, очевидно, совпадает с классом, где происходит доступ (B), поэтому доступ разрешен. [Примечание: int x = A(42).a
не допускается в пределах B.]
So &(A::a)
внутри B::show()
означает то же, что и &(this->a)
, и это простой указатель на int.