Was sind die Pointer-to-Member-Operatoren -> * und. * In C ++?

71

Ja, ich habe diese Frage und diese FAQ gesehen , aber ich verstehe immer noch nicht, was ->*und was .*in C ++ bedeutet.
Diese Seiten enthalten Informationen zu den Operatoren (z. B. Überladung), scheinen jedoch nicht gut zu erklären, was sie sind .

Was sind ->*und .*in C ++ und wann müssen Sie sie im Vergleich zu ->und verwenden .?

user541686
quelle

Antworten:

77

Ich hoffe, dieses Beispiel wird die Dinge für Sie klären

//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();

Jetzt können Sie es nicht verwenden x.somePointer(), oder px->somePointer()weil es in Klasse X kein solches Mitglied gibt. Dafür wird die spezielle Syntax des Zeigeraufrufs für Elementfunktionen verwendet. Probieren Sie einfach einige Beispiele selbst aus, Sie werden sich daran gewöhnen

Armen Tsirunyan
quelle
2
Fügen Sie die erforderlichen zusätzlichen Klammern hinzu, um die Priorität des Bedieners auszugleichen.
Martin York
2
@Armen: Ohh, ich sehe, was jetzt passiert ... Ich habe so etwas noch nie gesehen oder gebraucht. Es ist cool (+1), aber ich denke, die Frage ist jetzt: Wie pointerunterscheidet sich ein normaler Funktionszeiger von einer anderen Syntax? (zB Ist es größer?)
user541686
@Mehrdad: Der Punkt ist, dass Sie zum Aufrufen einer normalen Funktion nur die Argumente angeben. Bei nicht statischen Elementfunktionen benötigen Sie jedoch auch ein Objekt, für das Sie die Funktion aufrufen möchten. Daher die neue Syntax.
Armen Tsirunyan
1
Ein Zeiger-to-Mitglied-Funktion kann größer sein als eine normale Zeiger-to-Funktion: speziell mit Vererbung zu bewältigen iirc
Useless
Kann jemand erklären, wie das &X::ffunktioniert? Siehe diese Frage.
user2141130
21

BEARBEITEN: Übrigens wird es für Zeiger auf Funktionen virtueller Elemente seltsam .

Für Mitgliedsvariablen:

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;
}

Mitgliedsfunktionen sind fast gleich.

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 ();
}
Spraff
quelle
3
+1, um zu zeigen, dass die Syntax für alle Mitglieder gilt, nicht nur für Mitgliederfunktionen. Ich finde, dass Zeiger auf Mitgliedsvariablen trotz ihrer vielen interessanten Anwendungsmöglichkeiten sehr selten verwendet werden.
Jon Purdy
15

Kurz gesagt: Sie verwenden ->und .wenn Sie wissen, auf welches Mitglied Sie zugreifen möchten. Und du benutzt ->*und .*wenn nicht wissen, auf welches Mitglied Sie zugreifen möchten.

Beispiel mit einer einfachen aufdringlichen Liste

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;
};
Johannes Schaub - litb
quelle
6
+1 für den ersten Satz, obwohl ich noch nie in meinem Leben nicht gewusst habe, auf welches Mitglied ich zugreifen möchte, haha. :)
user541686
7

Sogenannte "Zeiger" auf Mitglieder in C ++ sind intern eher Offsets. Sie benötigen sowohl einen solchen Element- "Zeiger" als auch ein Objekt, um auf das Element im Objekt zu verweisen. Mitgliedszeiger werden jedoch mit Zeigersyntax verwendet, daher der Name.

Es gibt zwei Möglichkeiten, wie Sie ein Objekt zur Hand haben können: Sie haben einen Verweis auf das Objekt oder Sie haben einen Zeiger auf das Objekt.

Verwenden .*Sie für die Referenz, um sie mit einem Elementzeiger zu kombinieren, und für den Zeiger, ->*um sie mit einem Elementzeiger zu kombinieren.

Verwenden Sie jedoch in der Regel keine Elementzeiger, wenn Sie dies vermeiden können.

Sie befolgen ziemlich kontraintuitive Regeln und ermöglichen es, den protectedZugriff ohne explizites Casting zu umgehen , das heißt, versehentlich ...

Prost & hth.,

Prost und hth. - Alf
quelle
+1 für eine gute Erklärung ohne Code. :) Frage: Warum können wir nicht einfach die Adresse der Funktion wie die einer normalen Funktion nehmen? Unterscheidet sich der Zeiger auf eine Elementfunktion von einem Zeiger auf eine andere Funktion? (zB ist es größer?)
user541686
1
@Mehrdad: Wenn Sie einen Zeiger auf die Mitgliedsfunktion haben könnten, der auf nicht virtuelle Mitgliedsfunktionen beschränkt war, dann könnte es sich tatsächlich nur um die Adresse handeln. Die Virtualität oder nicht ist jedoch nicht Teil des Elementfunktionszeigertyps. Daher muss seine Darstellung einige Informationen darüber enthalten, ob sich der aktuelle Wert auf eine virtuelle Funktion bezieht oder nicht, und falls virtuell, für eine vtable-basierte Implementierungsinformation, die einen Offset in der vtable der Klasse bestimmt, der der Zeigertyp zugeordnet ist.
Prost und hth. - Alf
7

Wenn Sie einen normalen Zeiger (auf ein Objekt oder einen Basistyp) haben, können Sie ihn *dereferenzieren:

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

Konzeptionell machen wir dasselbe mit einem Elementfunktionszeiger:

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  ();

Ich hoffe, das hilft, das Konzept zu erklären. Wir dereferenzieren effektiv unseren Zeiger auf die Elementfunktion. Es ist etwas komplizierter - es ist kein absoluter Zeiger auf eine Funktion im Speicher, sondern nur ein Offset, der auf foooder pdarüber angewendet wird . Aber konzeptionell dereferenzieren wir es, ähnlich wie wir einen normalen Objektzeiger dereferenzieren würden.

Tim
quelle
5

Sie können den Zeiger auf Mitglieder nicht als normale Zeiger dereferenzieren, da für Elementfunktionen ein Zeiger erforderlich thisist und Sie ihn irgendwie übergeben müssen. Sie müssen also diese beiden Operatoren verwenden, mit Objekt auf einer Seite und Zeiger auf einer anderen, z (object.*ptr)().

Erwägen Sie jedoch die Verwendung von functionund bind( std::oder boost::, je nachdem, ob Sie C ++ 03 oder 0x schreiben) anstelle dieser.

Cat Plus Plus
quelle
Ich denke, das ist hier vielleicht die beste Erklärung.
Marcin
1

Zugriffsoperatoren zwischen Zeigern .*und Mitgliedern: und->*

Die Zugriffsoperatoren Zeiger auf Mitglied.* und ->*dienen zum Dereferenzieren eines Zeigers auf ein Mitglied in Kombination mit einem Objekt bzw. einem Zeiger auf ein Objekt . Diese Beschreibung gilt sowohl für Zeiger auf Datenelemente als auch für Zeiger auf Elementfunktionen .

Betrachten Sie zum Beispiel die Klasse Foo:

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

Wenn Sie einen Elementzeiger deklarieren iPtr, auf ein intDatenelement von Foo:

int Foo::* iPtr;

Sie können diesen Elementzeiger iPtrso initialisieren , dass er auf das Foo::iElement zeigt:

iPtr = &Foo::i;

Um diesen Zeiger zu dereferenzieren, müssen Sie ihn in Verbindung mit einem FooObjekt verwenden.

Betrachten Sie nun das Objekt foound den Zeiger auf das Objekt fooPtr:

Foo foo;
Foo* fooPtr = &foo;

Dann können Sie iPtrin Kombination mit foooder dereferenzieren fooPtr:

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

Analog können Sie .*und ->*mit Zeigern auf Funktionselemente verwenden . Beachten Sie jedoch, dass Sie sie in Klammern setzen müssen, da der Funktionsaufrufoperator , dh() hat eine höhere Priorität als die beiden .*und ->*:

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

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

Fazit: Sie benötigen ein Objekt, um einen Zeiger auf ein Element zu dereferenzieren, und welches Sie entweder .*oder ->*zum Dereferenzieren des Zeigers auf das Element verwenden, hängt davon ab, ob dieses benötigte Objekt direkt oder über einen Objektzeiger bereitgestellt wird.

C ++ 17 - Verwenden Sie std::invoke()stattdessen

Die Verwendung beider Operatoren kann seit C ++ 17 durch die std::invokeFunktionsvorlage ersetzt werden. std::invokeBietet eine einheitliche Möglichkeit zum Dereferenzieren von Elementzeigern, unabhängig davon, ob Sie sie in Kombination mit einem Objekt oder einem Objektzeiger verwenden , und unabhängig davon, ob der Zeiger auf Element einem Zeiger auf Datenelement oder Zeiger auf Elementfunktion entspricht :

// 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

Diese einheitliche Syntax entspricht der normalen Funktionsaufrufsyntax und erleichtert möglicherweise das Schreiben von generischem Code.

ロ ー ウ
quelle