Попытка понять "указатель на члена"
Я пытаюсь понять, как работает "указатель на член", но не все понятно для меня.
Вот пример класса:
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, потому что ни один объект не был создан, конструктор не был вызван.