Что такое операторы Pointer-to-Member -›* и .* в C++?

Да, я видел этот вопрос и это часто задаваемые вопросы, но я до сих пор не понимаю, что ->* и .* означают в C++.
Эти страницы предоставляют информацию об операторах (например, о перегрузке), но, похоже, не объясняют, что они являются.

Что такое ->* и .* в C++ и когда их нужно использовать по сравнению с -> и .?


person user541686    schedule 05.07.2011    source источник


Ответы (7)


Я надеюсь, что этот пример прояснит для вас ситуацию

//we have a class
struct X
{
   void f() {}
   void g() {}
};

typedef void (X::*pointer)();
//ok, let's take a pointer and assign f to it.
pointer somePointer = &X::f;
//now I want to call somePointer. But for that, I need an object
X x;
//now I call the member function on x like this
(x.*somePointer)(); //will call x.f()
//now, suppose x is not an object but a pointer to object
X* px = new X;
//I want to call the memfun pointer on px. I use ->*
(px ->* somePointer)(); //will call px->f();

Теперь вы не можете использовать x.somePointer() или px->somePointer(), потому что в классе X нет такого члена. Для этого используется специальный синтаксис вызова указателя функции-члена... просто попробуйте сами несколько примеров, вы привыкнете к этому

person Armen Tsirunyan    schedule 05.07.2011
comment
Добавьте необходимые дополнительные фигурные скобки, чтобы компенсировать приоритет оператора. - person Martin York; 05.07.2011
comment
@Armen: О, я вижу, что сейчас происходит... Я никогда раньше не видел и не нуждался в чем-то подобном. Это круто (+1), но я думаю, теперь возникает вопрос: чем pointer отличается от обычного указателя на функцию тем, что он требует другого синтаксиса? (например, больше?) - person user541686; 05.07.2011
comment
@Mehrdad: Дело в том, что для вызова обычной функции вы просто предоставляете аргументы. Но в случае нестатических функций-членов вам также нужен объект, для которого вы хотите вызвать функцию. Отсюда и новый синтаксис. - person Armen Tsirunyan; 05.07.2011
comment
Функция-указатель на-член может быть больше, чем обычная функция-указатель: специально для того, чтобы справиться с наследованием iirc - person Useless; 05.07.2011
comment
Кто-нибудь может объяснить, как работает &X::f? См. это вопрос. - person user2141130; 20.07.2013
comment
Думайте об этом как об относительном указателе: относительно объекта, который вам затем нужно будет предоставить, чтобы действительно достичь конечного пункта назначения. - person Sz.; 09.08.2019

РЕДАКТИРОВАТЬ: Кстати, это становится странным для указателей виртуальных функций-членов.

Для переменных-членов:

struct Foo {
   int a;
   int b;
};


int main ()
{
    Foo foo;
    int (Foo :: * ptr);

    ptr = & Foo :: a;
    foo .*ptr = 123; // foo.a = 123;

    ptr = & Foo :: b;
    foo .*ptr = 234; // foo.b = 234;
}

Функции-члены почти одинаковы.

struct Foo {
   int a ();
   int b ();
};


int main ()
{
    Foo foo;
    int (Foo :: * ptr) ();

    ptr = & Foo :: a;
    (foo .*ptr) (); // foo.a ();

    ptr = & Foo :: b;
    (foo .*ptr) (); // foo.b ();
}
person spraff    schedule 05.07.2011
comment
+1 за демонстрацию того, что синтаксис применяется ко всем членам, а не только к функциям-членам. Однако я обнаружил, что указатели на переменные-члены используются очень редко, несмотря на множество довольно интересных потенциальных применений. - person Jon Purdy; 05.07.2011

В двух словах: вы используете -> и ., если знаете, к какому участнику хотите получить доступ. И вы используете ->* и .*, если вы не знаете, к какому члену хотите получить доступ.

Пример с простым навязчивым списком

template<typename ItemType>
struct List {
  List(ItemType *head, ItemType * ItemType::*nextMemPointer)
  :m_head(head), m_nextMemPointer(nextMemPointer) { }

  void addHead(ItemType *item) {
    (item ->* m_nextMemPointer) = m_head;
    m_head = item;
  }

private:
  ItemType *m_head;

  // this stores the member pointer denoting the 
  // "next" pointer of an item
  ItemType * ItemType::*m_nextMemPointer;
};
person Johannes Schaub - litb    schedule 05.07.2011
comment
+1 за первое предложение, хотя никогда в жизни я не знал, к какому участнику я хочу получить доступ, ха-ха. :) - person user541686; 05.07.2011

Так называемые "указатели" на элементы в С++ больше похожи на смещения внутри. Вам нужен как такой «указатель» члена, так и объект, чтобы ссылаться на член в объекте. Но «указатели» членов используются с синтаксисом указателя, отсюда и название.

Есть два способа получить объект под рукой: у вас есть ссылка на объект или у вас есть указатель на объект.

Для ссылки используйте .*, чтобы объединить его с указателем члена, а для указателя используйте ->*, чтобы объединить его с указателем члена.

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

Они подчиняются довольно нелогичным правилам и позволяют обойти protected доступ без какого-либо явного приведения, то есть непреднамеренно

Ура и чт.,

person Cheers and hth. - Alf    schedule 05.07.2011
comment
+1 за хорошее объяснение без кода. :) Вопрос: Почему нельзя просто взять адрес функции, как у обычной функции? Отличается ли указатель на функцию-член от указателя на какую-либо другую функцию? (например, он больше?) - person user541686; 05.07.2011
comment
@Mehrdad: Если бы у вас мог быть указатель на функцию-член, которая была ограничена не виртуальными функциями-членами, то это действительно мог бы быть просто адрес. Однако виртуальность или нет не является частью типа указателя функции-члена. И поэтому его представление должно включать некоторую информацию о том, относится ли текущее значение к виртуальной функции или нет, и если виртуальное, то для реализации на основе виртуальной таблицы информация, которая определяет смещение в виртуальной таблице класса, с которым связан тип указателя. - person Cheers and hth. - Alf; 21.10.2014

Когда у вас есть обычный указатель (на объект или базовый тип), вы должны использовать * для его разыменования:

int a;
int* b = a;
*b = 5;     // we use *b to dereference b, to access the thing it points to

Концептуально мы делаем то же самое с указателем на функцию-член:

class SomeClass
{
   public:  void func() {}
};

// typedefs make function pointers much easier.
// this is a pointer to a member function of SomeClass, which takes no parameters and returns void
typedef void (SomeClass::*memfunc)();

memfunc myPointer = &SomeClass::func;

SomeClass foo;

// to call func(), we could do:
foo.func();

// to call func() using our pointer, we need to dereference the pointer:
foo.*myPointer();
// this is conceptually just:    foo  .  *myPointer  ();


// likewise with a pointer to the object itself:
SomeClass* p = new SomeClass;

// normal call func()
p->func();

// calling func() by dereferencing our pointer:
p->*myPointer();
// this is conceptually just:    p  ->  *myPointer  ();

Я надеюсь, что это поможет объяснить концепцию. Мы эффективно разыменовываем наш указатель на функцию-член. Это немного сложнее — это не абсолютный указатель на функцию в памяти, а просто смещение, которое применяется к foo или p выше. Но концептуально мы разыменовываем его так же, как разыменовываем обычный указатель объекта.

person Tim    schedule 05.07.2011

Вы не можете разыменовать указатель на члены как обычные указатели — потому что функции-члены требуют указателя this, и вы должны как-то передать его. Итак, вам нужно использовать эти два оператора с объектом с одной стороны и указателем с другой, например. (object.*ptr)().

Однако рассмотрите возможность использования function и bind (std:: или boost::, в зависимости от того, пишете ли вы C++03 или 0x) вместо них.

person Cat Plus Plus    schedule 05.07.2011
comment
Я думаю, что это может быть лучшим объяснением здесь. - person Marcin; 05.07.2011

Операторы доступа по указателю на член: .* и ->*

Операторы доступа указателя на член, .* и ->*, предназначены для разыменования указателя на член в сочетании с объектом и указателем на объект соответственно. Это описание применимо как к указателям на данные-члены, так и к указателям на функции-члены.

Например, рассмотрим класс Foo:

struct Foo {
   int i;
   void f();
};

Если вы объявляете указатель члена iPtr на элемент данных int элемента Foo:

int Foo::* iPtr;

Вы можете инициализировать этот указатель члена iPtr так, чтобы он указывал на член Foo::i:

iPtr = &Foo::i;

Чтобы разыменовать этот указатель, вам нужно использовать его вместе с объектом Foo.

Рассмотрим теперь объект foo и указатель на объект fooPtr:

Foo foo;
Foo* fooPtr = &foo;

Затем вы можете разыменовать iPtr в сочетании с foo или fooPtr:

foo.*iPtr = 0;
fooPtr->*iPtr = 0;

Аналогичным образом вы можете использовать .* и ->* с указателями на функции-члены. Однако обратите внимание, что вам нужно будет заключить их в круглые скобки, поскольку оператор вызова функции, то есть (), имеет более высокий приоритет, чем .* и ->*:

void (Foo::*memFuncPtr)() = &Foo::f;

(foo.*memFuncPtr)();
(fooPtr->*memFuncPtr)();

В заключение: вам нужен объект для разыменования указателя на член, и какой из них вы используете, .* или ->* для разыменования указателя на член, зависит от того, предоставляется ли этот необходимый объект напрямую или через указатель объекта.

C++17 — вместо этого используется std::invoke()

Использование обоих операторов можно заменить, начиная с C++17, на std::invoke шаблон функции. std::invoke обеспечивает унифицированный способ разыменования указателей на элементы независимо от того, используете ли вы их в сочетании с объектом или указателем объекта, а также независимо от того, используется ли указатель на элемент соответствует указателю на член данных или указателю на функцию-член:

// dereference a pointer to a data member
std::invoke(iPtr, foo) = 0;      // with an object
std::invoke(iPtr, fooPtr) = 0;   // with an object pointer

// dereference a pointer to a member function
std::invoke(memFuncPtr, foo);      // with an object
std::invoke(memFuncPtr, fooPtr);   // with an object pointer

Этот унифицированный синтаксис соответствует обычному синтаксису вызова функций и может упростить написание универсального кода.

person 眠りネロク    schedule 19.08.2020