2014-10-05 10 views
68

Eine coole Sache mit C++ ist, dass Sie Variablen von Zeiger-zu-Mitglied-Typen erstellen können. Der häufigste Anwendungsfall scheint einen Zeiger auf eine Methode, um zu sein:Was ist ein `int foo :: * bar :: *`?

struct foo 
{ 
    int x() { return 5; } 
}; 

int (foo::*ptr)() = &foo::x; 
foo myFoo; 
cout << (myFoo.*ptr)() << '\n'; // prints "5" 

jedoch rumgespielt, erkannte ich, dass sie genauso gut auf Membervariablen zeigen:

struct foo 
{ 
    int y; 
}; 

int foo::*ptr = &foo::y; 
foo myFoo; 
myFoo.*ptr = 5; 
cout << myFoo.y << '\n'; // prints "5" 

Dies ist zielich abgefahren. Es führte mich zu einem weiteren Experiment: Was wäre, wenn Sie einen Zeiger auf ein Unterelement einer Struktur bekommen könnten?

struct foo 
{ 
    int y; 
}; 

struct bar 
{ 
    foo aFoo; 
}; 

int bar::*foo::*ptr; 

Diese actually compiles.

Allerdings habe ich keine Ahnung, wie man es sinnvoll nutzen kann. Keiner der folgenden Werke:

int bar::*foo::*ptr = &bar::foo::y; // no member named "foo" in "bar" 
int bar::*foo::*ptr = &bar::aFoo::y; // no member named "aFoo" in "bar" (??) 
int bar::*foo::*ptr = &foo::y; // can't init 'int bar::*foo::*' with 'int foo::*' 

Des Weiteren nach dem Fehler, dass dies erzeugt, scheint es, dass diese Art nicht genau das, was ich im Sinn haben:

int bar::*foo::*ptr = nullptr; 
bar myBar; 
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible 
       // with object type ‘bar’ 

Es scheint, dass dieses Konzept ausweicht mich. Offensichtlich kann ich nicht ausschließen, dass es auf eine ganz andere Weise geparst wird, als ich es erwartet hätte.

Würde mir bitte jemand erklären, was ein int bar::*foo::* eigentlich ist? Warum sagt GCC mir, dass ein Zeiger auf ein Mitglied von bar mit einem bar Objekt nicht kompatibel ist? Wie würde ich eine int bar::*foo::* verwenden, und wie würde ich eine gültige konstruieren?

+0

Das sieht nach einem Umweg aus, was 'decltype()' mit entsprechenden Templates schon liefert? – slashmais

+6

Vielleicht weiß ich nicht genug über das Thema, aber wenn es hilft, 'int bar :: * foo :: * ptr = * neuer decltype (ptr);' kompiliert, aber ich weiß nicht, was es tut und ich kann nichts mit dem Ergebnis zu tun scheinen. Es ist auch ein Speicherleck, aber manchmal muss die Erinnerung im Namen der Wissenschaft geopfert werden. – IllusiveBrian

+0

Meine Güte, wenn die Autoren des Compilers dies implementiert haben, schwöre ich, dass sie eine Menge darüber schworen, wer zum Teufel jemals das benutzen wird. – marczellm

Antwort

53

Hier ist eine „gültige“ Art und Weise, eine solche Ungeheuerlichkeit der Initialisierung:

struct bar; 

struct foo 
{ 
    int y;  
    int bar::* whatever; 
}; 

struct bar 
{ 
    foo aFoo; 
}; 

int bar::* foo::* ptr = &foo::whatever; 

Wie wir sehen können, ist ptr ein Zeiger auf ein Mitglied foo (foo::*, von rechts nach links zu lesen), wo das Mitglied ist selbst ein Zeiger auf ein Mitglied von bar (bar::*), wo dieses Mitglied ein Int ist.

Wie würde ich einen int bar :: * foo :: *

Sie würden nicht, hoffentlich! Aber wenn Sie unter Zwang sind, versuchen Sie es!

struct bar 
{ 
    foo aFoo; 

    int really; 
}; 

int bar::* foo::* ptr = &foo::whatever; 
foo fleh; 
fleh.whatever = &bar::really; 
bar blah; 
blah.*(fleh.*ptr) = 42; 
std::cout << blah.really << std::endl; 
+40

Ich bin mir nicht sicher, ob ich verstehe, wie das geparst wird, aber das wäre nicht das erste Mal, dass ich nicht verstehe, wie C++ geparst wird. – zneak

+0

@zneak Ich wünschte, ich könnte Ihren Kommentar verbessern. – Jeremy

+5

+1 für "Sie würden nicht, hoffentlich!". –

19

, die einen Zeiger auf ein Datenelement wäre, das sich um einen Zeiger auf ein Datenelement (ein Mitglied der intbar).

Fragen Sie mich nicht, was es tatsächlich nützlich ist für - mein Kopf ist ein wenig :)

EDIT Spinnen: Hier ist ein vollständiges Beispiel davon in Aktion:

#include <iostream> 

struct bar { 
    int i; 
}; 

struct foo { 
    int bar::* p; 
}; 

int main() 
{ 
    bar b; 
    b.i = 42; 

    foo f; 
    f.p = &bar::i; 

    int bar::*foo::*ptr = &foo::p; 
    std::cout << (b.*(f.*ptr)); 
} 

Ausgang ist, von natürlich, 42.

Es kann noch mehr Spaß bekommen - hier einige Zeiger auf Member-Funktionen, die Zeiger auf Member-Funktionen zurück:

#include <iostream> 

struct bar { 
    int f_bar(int i) { return i; }; 
}; 

struct foo { 
    int(bar::*f_foo())(int) 
    { 
     return &bar::f_bar; 
    } 
}; 

int main() 
{ 
    int(bar::*((foo::*ptr)()))(int) = &foo::f_foo; 

    bar b; 
    foo f; 

    std::cout << (b.*((f.*ptr)()))(42); 
} 
+0

Gerade als ich dachte, ich fing an, einige dieser Aussagen zu verstehen, zerbrach dein zweites Beispiel meine Hoffnungen. –

+0

Ich verstehe, warum das zweite Beispiel Ihre Hoffnungen zerschmettert :). – hlide

+0

1) bar definiert eine Methode namens f_bar mit einem Integer-Argument und gibt eine ganze Zahl zurück. Bisher einfach zu verstehen. 2) foo definiert eine Methode f_foo, die keine Argumente hat und einen Zeiger auf eine Methode von bar zurückgibt, die ein ganzzahliges Argument hat und eine Ganzzahl zurückgibt. Weniger leicht zu lesen, gebe ich zu. 3) main first definiert ein lokales 'ptr' als einen Zeiger auf eine Methode von foo ohne Argument und gibt einen Zeiger auf eine Methode von bar mit einem Ganzzahlargument zurück und gibt eine ganze Zahl zurück und ordnet es mit f_foo Methode von foo zu. Es ist so lange her, es zu beschreiben! Fortsetzung folgt! – hlide

13

Lassen Sie uns die Erklärung int bar::*foo::*ptr; analysieren.

§8.3.3 [dcl.mptr]/p1:

In einer Erklärung T D wo D die Form hat

nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1 

und der nested-name-Spezifizierer eine Klasse bezeichnet und Der Typ des Identifikators in der Deklaration T D1 ist "abgeleitete-Deklarator-Typ-ListeT", dann der Typ der Kennung von D sind „abgeleitete -declarator-Typ-Listecv-Qualifier-seq Zeiger an dem Mitglied der Klasse nested-name-Spezifizierer vom Typ T“.

  • Schritt 1: Dies ist eine Erklärung der obigen Form, wo T = int, nested-name-Spezifizierer = bar:: und D1 = foo::* ptr. Wir betrachten zuerst die Deklaration T D1 oder int foo::* ptr.

  • Schritt 2: Wir wenden die gleiche Regel erneut an. int foo::* ptr ist eine Erklärung der obigen Form, wo T = int, nested-name-Spezifizierer = foo:: und D1 = ptr. Offensichtlich ist der Typ der Kennung in int ptr "int", daher ist der Typ der Kennung ptr in der Deklaration int foo::* ptr "Zeiger auf Mitglied der Klasse foo vom Typ int".

  • Schritt 3. Wir gehen zurück zur ursprünglichen Erklärung; der Typ des Bezeichners in T D1 (int foo::* ptr) ist „Zeiger auf Member der Klasse des Typs fooint“ pro Schritt 2, so dass die abgeleitete-declarator-Typ-Liste ist „Zeiger auf Member der Klasse foo vom Typ“. Die Substitution sagt uns, dass diese Deklaration ptr als "Zeiger auf Member der Klasse foo vom Typ Zeiger auf Member der Klasse bar vom Typ int" deklariert.

Hoffentlich müssen Sie nie eine solche Monstrosität verwenden.

+0

Gab es Unterschiede, wenn wir 'int bar :: * (foo :: * ptr) = nullptr;' stattdessen? –

+0

@DmitryFucintv Nein, die Bindung ist die gleiche. –

3

Falls jemand sich fragt, können Sie keinen Pointer-to-Member erstellen, der mehrere Ebenen tief verschachtelt. Der Grund dafür ist, dass alle Zeiger-zu-Elemente tatsächlich viel komplizierter sind als das, was sie auf den ersten Blick sehen; Sie enthalten nicht einfach einen bestimmten Offset für dieses spezifische Mitglied.

Die Verwendung eines einfachen Offset funktioniert nicht wegen der virtuellen Vererbung und dergleichen; Grundsätzlich kann es vorkommen, dass die Offsets eines bestimmten Feldes auch innerhalb eines einzelnen Typs zwischen den Instanzen variieren und somit die Auflösung von Zeiger zu Stab zur Laufzeit erfolgen muss. Meistens liegt das daran, dass der Standard nicht angibt, wie das interne Layout für Nicht-POD-Typen funktionieren könnte, so dass es nicht möglich ist, es statisch zu machen. Wenn dies der Fall ist, kann eine zweistufige Tiefenauflösung nicht mit einem normalen Zeiger-zu-Element ausgeführt werden, sondern der Compiler würde einen Zeiger erzeugen müssen, so dass er die doppelte Information eines Ein-Tiefen-Zeigers enthält -zu-Mitglied.

Ich stelle mir vor, dass da Zeiger zu Mitglied nicht so häufig sind, es keine Notwendigkeit gibt, eine Syntax zu erstellen, um mehrschichtige tiefe Mitglieder zu setzen, wenn Sie immer noch mehrere Zeiger verwenden können, um das gleiche Ergebnis zu erzielen.

1

zuerst die "Lesbarkeit" helfen, könnten Sie Klammer (Kompilieren funktioniert):

struct bar; 

struct foo 
{ 
    int y; 
    int (bar:: *whatever); // whatever is a pointer upon an int member of bar. 
}; 

struct bar 
{ 
    foo aFoo; 
}; 

// ptr is a pointer upon a member of foo which points upon an int member of bar. 
int (bar:: *(foo:: *ptr)) = &foo::whatever; 

Beachten Sie, dass

int (bar :: * was auch immer)

entspricht

int (* was auch immer)

mit einer Einschränkung über die Mitgliedschaft von bar.

Wie für

int (bar :: * (foo :: * ptr))

, ist es gleichwertig

int (* (* ptr))

mit zwei Einschränkungen für die Mitgliedschaft von foo und bar.

Sie sind nur Zeiger. Sie prüfen nicht, ob bar oder foo wirklich ein kompatibles Mitglied haben, da dies die Verwendung einer Vorwärtsdeklaration der Klassenleiste verhindern würde und die Klassenleiste nicht überprüft, ob andere Klassen durch Zeiger auf ihre Mitglieder verweisen. Außerdem müssen Sie möglicherweise auch eine undurchsichtige Klasse referenzieren (dh, eine Klassenleiste muss in einer separaten Einheit definiert sein).

Was ist mit der Nützlichkeit? Vielleicht für C++ Reflektion als eine Möglichkeit, den Wert eines Members einer Klasse über einen Klassenwrapper festzulegen/abzurufen?

template< typename Class, typename Type > 
struct ClassMember 
{ 
    using MemberPointer = Type (Class:: *); 
    MemberPointer member; 
    ClassMember(MemberPointer member) : member(member) {} 
    void operator()(Class & object, Type value) { object.*member = value; } 
    Type operator()(Class & object) { return object.*member; } 
}; 

template< typename Class, typename Type > ClassMember< Class, Type > MakeClassMember(Type(Class:: *member)) 
{ 
    return ClassMember< Class, Type >(member); 
} 

struct Rectangle 
{ 
    double width; 
    double height; 

    Rectangle(double width = 0., double height = 0.) : width(width), height(height) {} 
}; 

int main(int argc, char const *argv[]) 
{ 
    auto r = Rectangle(2., 1.); 

    auto w = MakeClassMember(&Rectangle::width); 
    auto h = MakeClassMember(&Rectangle::height); 

    w(r, 3.); 
    h(r, 2.); 

    printf("Rectangle(%f, %f)\n", w(r), h(r)); 

    return 0; 
} 

Sicher, dieses Beispiel zeigt nicht eine bestimmte Verwendung eines Doppelelementzeigers, weil ich es nicht eine einfache Art und Weise sehen, hier zu illustrieren oder einen guten Grund, so konzeptionell zu tun zu sprechen.

Verwandte Themen