Какво представляват операторите указател към член -›* и .* в 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

Така наречените "указатели" към членове в C++ са по-скоро като отмествания, вътрешно. Нуждаете се както от такъв "указател" на член, така и от обект, за да посочите члена в обекта. Но "указателите" на члена се използват със синтаксиса на указателя, оттук и името.

Има два начина да имате обект под ръка: имате препратка към обекта или имате указател към обекта.

За препратка използвайте .*, за да го комбинирате с указател на член, а за указателя използвайте ->*, за да го комбинирате с указател на член.

Въпреки това, като правило, не използвайте указатели на член, ако можете да го избегнете.

Те се подчиняват на доста контраинтуитивни правила и правят възможно заобикалянето на protected достъп без изрично кастинг, тоест по невнимание

Наздраве и чт.,

person Cheers and hth. - Alf    schedule 05.07.2011
comment
+1 за това, че го обясняваш добре без код. :) Въпрос: Защо просто не можем да вземем адреса на функцията като този на нормална функция? Различен ли е указателят към функция член от указател към друга функция? (напр. по-голям ли е?) - person user541686; 05.07.2011
comment
@Mehrdad: Ако можете да имате указател към членска функция, която е ограничена до невиртуални членски функции, тогава това наистина може да е просто адресът. Обаче виртуалността или не е част от типа указател на членска функция. И така неговото представяне трябва да включва известна информация за това дали текущата стойност се отнася до виртуална функция или не, и ако е виртуална, за информация за внедряване, базирана на vtable, която определя отместване във vtable на класа, с който е свързан типът указател. - 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