Попытка понять "указатель на члена"

Я пытаюсь понять, как работает "указатель на член", но не все понятно для меня.

Вот пример класса:

class T
{
public:
    int a;
    int b[10]; 
    void fun(){}
};

Следующий код иллюстрирует проблему и содержит вопросы:

void fun(){};

void main()
{
   T obj;                
   int local;
   int arr[10];
   int arrArr[10][10];

   int *p = &local;   // "standard" pointer
   int T::*p = &T::a; // "pointer to member" + "T::" , that is clear

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun;  
   //or
   void (T::*pF)() = &T::fun;  

   int *pA = arr; // ok
   int T::*pA = T::b; // error

   int (T::*pA)[10] = T::b; // error
   int (T::*pA)[10] = &T::b; //works;

//1. Why "&" is needed for "T::b" ? For "standard" pointer an array name is the representation of the 
//   address of the first element of the array. 

//2. Why "&" is not needed for the pointer to member function ? For "standard" pointer a function name 
//   is the representation of the function address, so we can write &funName or just funName when assigning to the pointer. 
//   That rule works there.

//3. Why the above pointer declaration looks like the following pointer declaration ?: 

   int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array. 
   system("pause");
}

Ответы

Ответ 1

Я думаю, что самое простое - забыть о членах класса на мгновение, а также указать указатели и распад.

int local;
int array[10];

int *p = &local; // "standard" pointer to int

Существует тенденция к тому, что люди говорят, что "распадающийся указатель" такой же, как указатель на массив. Но между arr и &arr существует важное различие. Первый не распадается на последний

int (*p_array_standard)[10] = &arr;

Если вы делаете &arr, вы получаете указатель на массив-10-int. Это отличается от указателя на массив-9-int. И это отличается от указателя на int. sizeof(*p_array_standard) == 10 * sizeof(int).

Если вы хотите, чтобы указатель на первый элемент, то есть указатель на int, с sizeof(*p) == sizeof(int)), вы можете сделать:

int *p_standard = &(arr[0);

Все до сих пор основано на стандартных/явных указателях.

В C есть специальное правило, которое позволяет заменить &(arr[0]) на arr. Вы можете инициализировать int* с помощью &(arr[0]) или с помощью arr. Но если вам действительно нужен указатель на массив, вы должны сделать int (*p_array_standard)[10] = &arr;

Я думаю, что распад может быть почти уволен как кусок синтаксического сахара. Затухание не меняет смысла любого существующего кода. Он просто позволяет использовать код, который в противном случае был бы незаконным, чтобы стать законным.

int *p = arr; // assigning a pointer with an array.  Why should that work?
                  // It works, but only because of a special dispensation.

Когда массив распадается, он распадается на указатель на один элемент int [10]int*. Он не распадается на указатель на массив, который будет int (*p)[10].


Теперь мы можем посмотреть на эту строку из вашего вопроса:

int (T::*pA3)[10] = T::b; // error

Опять же, член класса не имеет отношения к пониманию того, почему это не удалось. Тип слева - это указатель на массив-int, а не указатель-на-int. Поэтому, как мы говорили ранее, разложение не имеет значения, и вам нужно & получить тип указатель-массив-int-int.

Лучше спросить, почему это не работает (обновление: теперь я вижу, что у вас это было в вашем вопросе.)

int T::*pA3 = T::b;

Правая сторона выглядит как массив, а левая сторона - указатель на один элемент int *, и поэтому вы можете разумно спросить: почему здесь не работает распад?

Чтобы понять, почему здесь распад затруднен, пусть "отменит" синтаксический сахар и замените T::b на &(T::b[0]).

int T::*pA3 = &(T::b[0]);

Я думаю, что это вопрос, который вас интересует. Мы устранили гниение, чтобы сосредоточиться на реальной проблеме. Эта строка работает с объектами, не являющимися членами, почему он не работает с объектами-членами?

Простой ответ заключается в том, что стандарт не требует этого. Pointer-decay - это кусок синтаксического сахара, и они просто не указали, что он должен работать в таких случаях.

Указатели для членов в основном немного более суровые, чем другие указатели. Они должны указывать прямо на "сырую" сущность, как она появляется в объекте. (Извините, я имею в виду, что он должен ссылаться (косвенно), кодируя смещение между началом класса и местоположением этого члена. Но я не очень хорошо объясняю это.) Они не могут указывать на под-объекты, такие как первый элемент массива, или, действительно, второй элемент массива.

Q: Теперь у меня есть свой вопрос. Может ли разложение указателя быть расширенным для работы с такими массивами-членами? Я думаю, что это имеет смысл. Я не единственный, кто думает об этом! Подробнее см. . Это возможно, и я думаю, что ничего не мешает компилятору реализовать его как расширение. Субобъекты, включая элементы массива, имеют фиксированное смещение от начала класса, поэтому это довольно логично.

Ответ 2

Почему "&" требуется для "T:: b"?

Потому что это требует стандарт. Это делается для того, чтобы отличить его от доступа к статическому члену класса.

Из стандартного черновика n3337, пункта 5.3.1/4, внимание мое:

Указатель на член формируется только тогда, когда используется явный &, а его операнд - это квалифицированный идентификатор, не заключенный в круглых скобках. [Примечание: то есть выражение &(qualified-id), где квалификационный идентификатор заключен в круглые скобки, не образует выражения типа "указатель на член". Также не существует qualified-id, потому что нет неявного преобразования из quali-id-идентификатора для нестатической функции-члена в тип "указатель на член-функция", поскольку от значения типа функции к типу "указатель на функцию" (4.3) нет. &unqualified-id указатель на элемент, даже в пределах класса неквалифицированных идентификаторов. - конечная нота]


Для "стандартного" указателя имя массива представляет собой представление адреса первого элемента массива.

Не совсем. Массив автоматически преобразуется в указатель на первый элемент, если требуется. Имя массива - это массив, период.


Почему "&" не требуется для указателя на функцию-член?

Требуется . Если ваш компилятор позволяет это, он получил ошибку. См. Выше стандартное.


Для "стандартного" указателя имя функции является представлением адреса функции, поэтому мы можем написать & funName или просто funName при назначении указателю.

То же самое относится и к массивам. Там есть автоматическое преобразование, но в противном случае функция имеет тип функции.

Рассмотрим:

#include <iostream>

template<typename T, size_t N>
void foo(T (&)[N]) { std::cout << "array\n"; }

template<typename T>
void foo(T*) { std::cout << "pointer\n"; }

int main()
{
    int a[5];
    foo(a);
}

Вывод array.

Аналогично для указателей функций:

#include <iostream>

template<typename T>
struct X;

template<typename T, typename U>
struct X<T(U)> {
    void foo() { std::cout << "function\n"; }
};

template<typename T, typename U>
struct X<T(*)(U)> {
    void foo() { std::cout << "function pointer\n"; }
};

void bar(int) {}

int main()
{
    X<decltype(bar)> x;
    x.foo();
}

Вывод function.


И разъяснение об этом, потому что я не уверен, что именно ваш комментарий должен сказать:

int arrArr[10][10];
int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array.

Опять же, преобразование между массивами и указателями. Заметим, что элементами arrArr являются int[10] s. pAA указывает на первый элемент arrArr, который представляет собой массив из 10 целых чисел, расположенный в &arrArr[0]. Если вы увеличиваете значение pAA, оно будет равно &arrArr[1] (поэтому более подходящим было бы присвоение имени pA).

Если вам нужен указатель на arrArr в целом, вам нужно сказать:

int (*pAA)[10][10] = &arrArr;

Приращение pAA теперь приведет вас к концу arrArr, что на 100 ints.

Ответ 3

Первое, что нужно отметить, это то, что массивы распадаются на указатели на первый элемент.

int T::*pA = T::b;

Здесь есть две проблемы, а может быть одна или более двух... Первое - это подвыражение T::b. Элемент-член b не является статичным и к нему нельзя получить доступ с помощью этого синтаксиса. Для указателя на членов вам всегда нужно использовать адрес-оператора:

int T::*pa = &T::b;  // still wrong

Теперь проблема в том, что правая сторона имеет тип int (T::*)[10], который не совпадает с левой стороной, и это не скомпилируется. Если вы исправите тип слева, вы получите:

int (T::*pa)[10] = &T::b;

Это правильно. Путаница, возможно, возросла из-за того, что массивы имеют тенденцию распадаться на первый элемент, поэтому, возможно, проблема заключалась в предыдущем выражении: int *p = a;, который преобразуется компилятором в более явный int *p = &a[0];. Массивы и функции имеют тенденцию к распаду, но никакой другой элемент в языке не делает. И T::b не является массивом.


Изменить: я пропустил часть о функциях...

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun; 
   //or
   void (T::*pF)() = &T::fun;

Это может быть не так ясно, как кажется. Утверждение void (T::*pf)() = T::fun; является незаконным в С++, компилятор, который вы используете, принимает его без уважительной причины. Правильный код является последним: void (T::*pf)() = &T::fun;.

Ответ 4

Почему "&" требуется для "T:: b"?

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

Для "стандартного" указателя имя массива представляет собой представление адреса первого элемента массива.

Нет, это не так; он конвертируется в указатель на свой первый элемент из-за тайного правила преобразования, унаследованного от C. К сожалению, это породило широко распространенное (и неверное) убеждение в том, что массив является указателем. Такая путаница, вероятно, является частью причины не вводить подобные причудливые преобразования для указателей-членов.

Почему "&" не требуется для указателя на функцию-член?

Это. Однако ваш компилятор принимает неверный void main(), поэтому он может принять другой сломанный код.

Для "стандартного" указателя имя функции - это представление адреса функции, поэтому мы можем написать & funName или просто funName при назначении указателю.

Опять же, имя функции не является указателем; он просто конвертируется в один.

Почему вышеуказанное объявление указателя выглядит следующим образом:

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

Ответ 5

int (T::*pA)[10] = &T::b; //works;

3.Why the above pointer declaration looks like the following pointer declaration ?

int (*pAA)[10] = arrArr;

Чтобы понять это, нам не нужно путать себя с массивами-членами, простые массивы достаточно хороши. Скажем, мы два

int a[5];
int a_of_a[10][5];

Первый (самый левый) размер массива распадается, и мы получаем указатель на первый элемент массива, когда мы используем только имя массива. Например.

int *pa = a;                 // first element is an int for "a"
int (*pa_of_a)[5] = a_of_a;  // first element is an array of 5 ints for "a_of_a"

Таким образом, без использования оператора & в массиве, когда мы назначаем его имя указателям или передаем его функции в качестве аргументов, он распадается, как описано, и дает указатель на его первый элемент. Однако, когда мы используем оператор &, распад не происходит, так как мы запрашиваем адрес массива и не используем имя массива as-is. Таким образом, мы получим указатель на фактический тип массива без какого-либо распада. Например.

int (*paa) [5] = &a;                   // note the '&'
int (*paa_of_a) [10][5] = &a_of_a;

Теперь в вашем вопросе верхняя декларация является указателем на адрес массива без распада (одно измерение остается одним измерением), тогда как нижнее объявление является указателем на имя массива с распадом (два измерения становятся одним измерением). Таким образом, оба указателя относятся к массиву одного и того же размера и выглядят одинаково. В нашем примере

int (*pa_of_a)[5]
int (*paa) [5]

обратите внимание, что типы этих указателей одинаковы int (*) [5], хотя значение, на которое они указывают, имеют разные массивы.

Ответ 6

Потому что T на нем уже имеет четко определенный смысл: тип Class T. Таким образом, слова типа T::b логически используются для обозначения членов Class T. Чтобы получить адрес этих членов, нам нужен больше синтаксиса, а именно &T::b. Эти факторы не вступают в игру со свободными функциями и массивами.

Ответ 7

Указатель на класс или тип структуры указывает на объект в памяти.
Указатель на элемент типа класса фактически указывает на смещение от начала объекта.
Вы можете думать об этих указателях как о указателях на блоки памяти. Им нужен фактический адрес и смещение, поэтому &.

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

В сырой гайке-оболочке логика, требующая & получить адрес для членов и адрес объекта в целом.

Ответ 8

void (*pF)() = fun;        //here also everything is clear

Это не работает, потому что fun fun undefined

int T::*pA = T::b; // error

Что такое T:: b? T:: b не является статическим членом. Поэтому вам нужен конкретный объект. Вместо этого напишите

int *pA = &obj.b[0]; 

Аналогично,

int (T::*pA)[10] = &T::b; //works;

Он может быть скомпилирован. Но он не будет работать так, как вы ожидали. Сделайте b static или вызовите obj.b, чтобы получить доступ к определенному члену определенного объекта. Мы можем легко это проверить. Создайте конкструктор для вашего класса T

class T
{
public:
    T() {
        a = 444;
    }
    int a;
    int b[10]; 
    void fun(){}
};

На каких значениях pA?

 int T::*pA = &T::a; 

* pA не указывает на переменную со значением 444, потому что ни один объект не был создан, конструктор не был вызван.