C ++: доступ к элементу данных подструктуры с использованием указателя-к-члену [дубликат]

, вероятно, нет. Он поймет, что первое слово после определения - это имя идентификатора, а остальное - это «тело» этого.

17
задан benoitj 18 December 2009 в 19:46
поделиться

3 ответа

Да, это запрещено. Вы не первый, кто придумал эту совершенно логичную идею. По-моему, это один из очевидных «ошибок» / «упущений» в спецификации указателей на членов на C ++, но, по-видимому, комитет не заинтересован в дальнейшем развитии спецификации указателей на членов ( в случае с большинством «низкоуровневых» языковых функций).

Обратите внимание, что все необходимое для реализации функции уже существует на этом языке. Указатель на элемент a-data-of-a-member ничем не отличается от указателя на непосредственный элемент данных. Единственное, чего не хватает, это синтаксис для инициализации такого указателя. Однако комитет, по-видимому, не заинтересован в введении такого синтаксиса.

С чистой формальной логической точки зрения это должно было быть разрешено в C ++

struct Inner {
  int i;
  int j[10];
};

struct Outer {
  int i;
  int j[10];
  Inner inner;
};

Outer o;
int Outer::*p;

p = &Outer::i; // OK
o.*p = 0; // sets `o.i` to 0

p = &Outer::inner.i; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.i` to 0

p = &Outer::j[0]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[0]` to 0
// This could have been used to implement something akin to "array type decay" 
// for member pointers

p = &Outer::j[3]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[3]` to 0

p = &Outer::inner.j[5]; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.j[5]` to 0

. Типичная реализация элемента-указателя-данных - это не что иное, как просто байт-смещение члена от начала окружающего объекта. Поскольку все члены (непосредственные члены и члены членов) в конечном итоге последовательно выкладываются в памяти, члены членов также могут быть идентифицированы с помощью определенного значения смещения. Это то, что я имею в виду, когда говорю, что внутренняя работа этой функции уже полностью реализована, все, что необходимо, - это синтаксис инициализации.

На языке C эта функция эмулируется явными смещениями, полученными с помощью стандартного offsetof макрос. А в C я могу получить offsetof(Outer, inner.i) и offsetof(Outer, j[2]). К сожалению, эта возможность не отражается в C ++ указателях на данные.

17
ответ дан AnT 19 August 2018 в 17:50
поделиться
  • 1
    да, это должно было быть поддержано! черт!!!!!!!! – benoitj 18 December 2009 в 19:57
  • 2
    В C ++ 0x нам разрешено делать sizeof(Outer::inner.i) и decltype(Outer::inner.i). Интересно, в чем причина, которая делает элементы указателей на такие подэлементы более сложными? – Johannes Schaub - litb 19 December 2009 в 00:22
  • 3
    для вашего примера относительно указателя на массив, вы можете сделать: Outer o; int (Outer::*p_array)[10]; p_array = &Outer::j; o.*p_array[3] = 0; – Grim Fandango 10 February 2012 в 12:25
  • 4
    @Grim Fandango: Да, но это не так. Речь идет о способности указывать на подобъекты. – AnT 11 February 2012 в 01:52

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

Во-первых, важно понимать, что указатель на член в основном является смещением от указателя на структуру [1]. У языка есть оператор смещения, который подозрительно похож на этот, и, что интересно, дает нам выразительность, необходимую для выполнения того, что мы хотим.

Проблема, с которой мы немедленно сталкиваемся, заключается в том, что C ++ запрещает указывать указатели на элементы. Ну, почти ... у нас есть союз, заброшенный нашими рукавами. Как я уже сказал, это некрасиво!

. В конце концов, нам также нужно знать правильный тип указателя, который нужно отличить.

Итак, без дальнейших церемоний, вот код ( проверено на gcc и clang):

template <typename C, typename T, /*auto*/size_t P>
union MemberPointerImpl final {
  template <typename U> struct Helper
    { using Type = U C::*; };
  template <typename U> struct Helper<U&>
    { using Type = U C::*; };

  using MemberPointer = typename Helper<T>::Type;

  MemberPointer o;
  size_t i = P; // we can't do "auto i" - argh!
  static_assert(sizeof(i) == sizeof(o));
};

#define MEMBER_POINTER(C, M) \
  ((MemberPointerImpl<__typeof__(C),          \
      decltype(((__typeof__(C)*)nullptr)->M), \
      __builtin_offsetof(__typeof__(C), M)    \
  >{ }).o)

Сначала посмотрим на макрос MEMBER_POINTER. Он принимает два аргумента. Первая, C - это структура, которая будет базой для указателя-члена. Обертка его в __typeof__ не является строго необходимой, но позволяет передавать либо тип, либо переменную. Второй аргумент M предоставляет выражение тому, членом которого мы хотим указатель.

Макрос MEMBER_POINTER извлекает из этих аргументов две дополнительные части информации и передает их как параметры в MemberPointerImpl шаблон союза. Первая часть - это тип элемента, на который указывает. Это делается путем построения выражения с использованием нулевого указателя, на котором мы используем decltype. Вторая часть - это смещение от базовой структуры к рассматриваемому элементу.

Внутри MemberPointerImpl нам нужно построить тип MemberPointer, который будет тем, что возвращается макросом. Это делается с помощью вспомогательной структуры, которая удаляет ссылки, которые бесполезно возникают, если наш член является элементом массива, что также позволяет поддерживать это. Он также позволяет gcc и clang дать нам хороший полностью расширенный тип в диагностике, если мы назначим возвращаемое значение переменной с несогласованным типом.

Итак, чтобы использовать MEMBER_POINTER, просто измените свой код:

bool MyStruct::* toto = &MyStruct::inner.c;

to:

bool MyStruct::* toto = MEMBER_POINTER(MyStruct, inner.c);

[1] Хорошо, оговорка: это может быть неверно для всех архитектур / компиляторов, поэтому переносимые разработчики кода отвлекают взгляд сейчас!

1
ответ дан Andy G 19 August 2018 в 17:50
поделиться

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

bool InnerStruct::* toto2 = &InnerStruct::c;

Изменить: перечитывать ваш вопрос, я предполагаю, что вы хотите определить указатель на член внешней структуры и указать его прямо на член внутренней структуры. Это просто не разрешено. Чтобы добраться до члена внутренней структуры, которая содержится во внешней структуре, вам нужно будет создать указатель на внутреннюю структуру itelft, а затем на ее член. Чтобы использовать его, вы бы разделили оба указателя на члены:

// Pointer to inner member of MyStruct:
InnerStruct MyStruct::* toto = &MyStruct::inner;

// Pointer to c member of InnerStruct:
bool InnerStruct::* toto2 = &InnerStruct::c;

// Dereference both to get to the actual bool:
bool x = mystruct.*toto.*toto2;
19
ответ дан Jerry Coffin 19 August 2018 в 17:50
поделиться
  • 1
    +1 за хороший ответ – Ashish 18 December 2009 в 20:40
  • 2
    @JerryCoffin: Спасибо за отличный ответ. Как бы вы использовали это обозначение для ссылки на, например, элемент [3], из структуры & lt; code & gt; struct Outer {int i; int j [5]; } & Л; / код & GT; – Grim Fandango 9 February 2012 в 18:33
  • 3
    @GrimFandango: вы должны создать указатель на член, например: int (Outer::* ptr_j)[5] = &Outer::j;. Предполагая, что какой-то экземпляр x из Outer, вы бы разыменовали его так: (x.*ptr_j)[3] = 1;. – Jerry Coffin 9 February 2012 в 18:57
Другие вопросы по тегам:

Похожие вопросы: