У меня есть базовый класс A и производный класс B
класс B является производным от A как публичный
Я хочу получить доступ к адресу переменной члена, если A является классом A является переменной-членом
Я наблюдаю различное поведение, когда я использую защищенный и общедоступный спецификатор.
Когда член А класса А защищенный в этом случае я получаю:
cout<<&A::a << endl;
выдает мне ошибку компилятора ..
но cout<<&(A::a)<<endl;
является действительным и дает мне правильный результат.
и когда член класса А общественности в этом случае я получаю:
Почему это поведение?
Вот полный код:
#include <iostream>
using namespace std;
class A
{
protected:
int a;
void fun()
{
cout<<"A's fun"<<endl;
}
public:
A(int Aarg):a(Aarg)
{
cout << "A's construction done" <<endl;
}
};class B: public A
{
protected:
int b;
public:
void fun()
{
cout << "B's fun"<<endl;
}
B(int As, int Bs):A(As), b(Bs)
{
std::cout<<"B's Construction Done" <<std::endl;
}
void show()
{
A::fun();
//cout << a << endl;
cout<<&A::a << endl; //compilation error
}
};
int main()
{
B(10,20).show();
return 0;
}
Теперь я могу наблюдать неопределенное поведение:
Если я сделаю свою переменную-член a в классе A общедоступной, то не будет никакой ошибки компиляции, но вывод будет как 1, я не знаю почему ..
class A{
public:
int a
....
....
ВЫХОД:
А строительство сделано
Строительство Б завершено
А это весело
0027F91C
1 (почему 1) и никаких ошибок, которые я смог получить, когда попытался получить доступ к защищенному участнику?
Краткий ответ: не существует неопределенного поведения. Поведение, которое вы видите:
&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
,&(this->a)
), который является обычным указателем на int. Этот доступ к защищенному члену подобъекта базового класса *this
действителен, так что этот вариант можно использовать, даже если он защищен в A.Более длинное объяснение:
Стандарт гласит (5.3.1 / 3):
Результат одинарный & Оператор является указателем на свой операнд.
операнд должен быть lvalue или квалифицированным идентификатором. Если операнд
квалифицированный идентификатор с именем нестатического члена m некоторого класса C с типом T,
результат имеет тип «указатель на член класса C типа T» и является
значение, обозначающее C :: m. […]
Итак, выражение &A::a
пытается получить указатель на член члена a класса A.
В следующем пункте (5.3.1 / 4) уточняется, что только &Синтаксис X :: m создает указатель на член — ни то, ни другое &(X::m)
ни &m
или простой X::m
делать:
Указатель на член формируется только при явном & используется и его
операнд — это квалифицированный идентификатор, не заключенный в скобки.
Но такое выражение допустимо, только если доступ разрешен. В случае защищенного члена (11.4 / 1) применяется:
Дополнительная проверка доступа помимо описанной ранее в пункте 11
применяется, когда нестатический элемент данных или нестатическая функция-член
является защищенным членом своего класса именования (11.2), как описано
ранее доступ к защищенному члену предоставляется, потому что ссылка
происходит в другом или члене некоторого класса C. Если доступ к форме
указатель на член (5.3.1), спецификатор вложенного имени должен обозначать C
или класс, полученный из C. […]
В вашем случае доступ к защищенному члену a будет предоставлен, потому что ссылка на a происходит в члене класса B, производном от A. Когда выражение пытается сформировать указатель на член, вложенное имя спецификатор (часть перед окончательным «:: a») должна обозначать B. Таким образом, самая простая допустимая форма &B::a
, Форма &A::a
разрешено только в пределах членов или друзей самого класса А.
Отсутствует форматированный оператор вывода для указателей на член (ни как член istream, ни как свободная операторная функция), поэтому компилятор будет рассматривать перегрузки, которые можно вызвать с помощью стандартного преобразования (последовательности). Единственное стандартное преобразование указателей в член во что-то еще описано в 4.12 / 1:
Значение […] указателя на тип члена может быть преобразовано в
prvalue типа bool. […] значение указателя нулевого члена преобразуется
ложно; любое другое значение преобразуется в true. […]
Это преобразование можно использовать без дополнительных преобразований для вызова basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n)
, Другие перегрузки требуют более длинных последовательностей преобразования, так что перегрузка является лучшим соответствием.
Как &A::a
принимает адрес некоторого члена, это не нулевое значение указателя на член. Таким образом, он будет преобразован в 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.]
Так &(A::a)
в B::show()
означает так же, как &(this->a)
и это простой указатель на int.
Вы попадаете в небольшую причуду в синтаксисе (не то, чтобы в C ++ их было немного …). Доступ к переменной-члену по умолчанию осуществляется через прямое использование имени или через this->
, То есть, более простое написание вашей функции показа было бы:
void B::show() {
std::cout << a << std::endl; // alternatively this->a
std::cout << &a << std::endl; // &(this->a)
}
Который имеет простой и последовательный синтаксис. Теперь язык позволяет добавлять дополнительные квалификаторы для доступа к членам базы при доступе к члену:
std::cout << A::a << std::endl; // Extra qualification
Это действительно эквивалентно this->A::a
и основное использование дополнительной квалификации состоит в устранении неоднозначности (если на двух базах был член a
выберите один в A
) и в случае отключения виртуальной функции динамической отправки.
Теперь вы можете сделать то же самое с указателями, как в &this->A::a
, который возьмет адрес участника a
в подобъекте A
текущего объекта. Проблема в этом случае заключается в том, что вы не можете сбросить this->
классификатор, как синтаксис &A::a
зарезервировано для получения указателя на член, хотя путем добавления дополнительного набора скобок, как в &(A::a)
парсер больше не может интерпретировать это как получение указателя на член, а как получение адреса объекта, представленного A::a
, который, как видно ранее, является членом в базе.
Это проблема синтаксиса. & означает, что вы спрашиваете адрес элемента сразу справа.
Если вы напишите:
&A::a
Это как если бы вы написали
(&A)::a
Это означает, что вы запрашиваете доступ к «от» от&А, что не правильно.
& не применяется только к переменным, его также можно использовать для функции, см .: http://www.cprogramming.com/tutorial/function-pointers.html