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

У меня есть базовый класс A и производный класс B

класс B получен из A как общедоступный

Я хочу получить доступ к адресу переменной-члена, если A является классом a является переменной-членом

Я наблюдаю за другим поведением, когда я пользуюсь защищенным и общедоступным спецификатором доступа.

Когда член a класса A защищен, в этом случае я получаю:

cout<<&A::a << endl; бросает мне ошибку компилятора..

но cout<<&(A::a)<<endl; является допустимым и дает правильный результат.

и Когда член a класса A public, в этом случае я получаю:

Зачем это поведение?

Вот полный код:

#include <iostream>
using namespace std;

class A
{
    protected:
    int a;
    void fun() 
    {
    cout<<"A fun"<<endl;
    }

public:
    A(int Aarg):a(Aarg)
    {
        cout << "A construction done" <<endl;
    }
};


class B: public A
{
protected:
int b;
public:
void fun()
{
cout << "B fun"<<endl;
}

B(int As, int Bs):A(As), b(Bs)
{
std::cout<<"B 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;
}

Теперь существует поведение undefined, которое я могу наблюдать:

Если я сделаю свою переменную-член a в классе A общедоступной, тогда не будет какой-либо ошибки компиляции, но вывод идет как 1, я не знаю, почему..

class A{
public:
int a
....
....

ВЫВОД:

Сделанная конструкция

B Выполненная конструкция

Веселье

0027F91C

1 (почему 1) и никаких ошибок, которые я смог получить, когда пытался получить доступ к защищенному члену?

Ответ 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.

Ответ 2

В синтаксисе вы получаете небольшую причуду (не то, что у С++ их мало...). По умолчанию для доступа к переменной-члену используется прямое использование имени или через this->. То есть более простое написание вашей функции show будет:

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, который, как видно ранее, является членом в базе.

Ответ 3

Проблема с синтаксисом. Это означает, что вы запрашиваете адрес элемента сразу справа.

Если вы пишете:

&A::a

Как будто вы написали

(&A)::a

Это означает, что вы запрашиваете доступ к 'a' из '& A', что неверно.

The и не применяется только к переменным, его также можно использовать для функции, см. http://www.cprogramming.com/tutorial/function-pointers.html