2014-10-05 23 views
68

Fajną rzeczą w C++ jest to, że pozwala tworzyć zmienne typu wskaźnik-do-członka. Najczęstszym przypadkiem użycia wydaje się aby uzyskać wskaźnik do metody:Co to jest `int foo :: * bar :: *`?

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

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

Jednak aprowizacji, zdałem sobie sprawę, że mogą one równie dobrze wskazywać zmiennych składowych:

struct foo 
{ 
    int y; 
}; 

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

Jest całkiem rad. Doprowadziło mnie to do kolejnego eksperymentu: co by było, gdyby uzyskać wskaźnik do pod-członka struktury? To jest .

Jednak nie mam pojęcia, jak przypisać to coś użytecznego. Żadna z następujących robót:

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::*' 

Ponadto, w zależności od błędu, który to generuje, wydaje się, że tego typu nie jest dokładnie to, co mam na myśli:

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

Wydaje się, że koncepcja ta uchyla mnie. Oczywiście nie mogę wykluczyć, że zostanie on po prostu sparsowany w sposób całkowicie odmienny od tego, czego bym się spodziewał.

Czy ktoś mógłby mi wyjaśnić, czym właściwie jest int bar::*foo::*? Dlaczego gcc informuje mnie, że wskaźnik do elementu bar jest niezgodny z obiektem bar? Jak korzystać z int bar::*foo::* i jak mam utworzyć prawidłową?

+0

Wygląda to na okrężną próbę określenia, co 'decltype()' z odpowiednimi szablonami już dostarcza? – slashmais

+6

Może nie wiem wystarczająco dużo na ten temat, ale jeśli to pomaga, 'int bar :: * foo :: * ptr = * nowy decltype (ptr);' kompiluje, ale nie wiem co robi i ja nie może nic zrobić z wynikiem. To także wyciek pamięci, ale czasami pamięć musi być poświęcana w imię nauki. – IllusiveBrian

+0

Geez, kiedy twórcy kompilacji zaimplementowali to, przysięgam, że przysięgali dużo o tym, kto to kiedykolwiek użyje. – marczellm

Odpowiedz

53

Oto „poprawny” sposób inicjowania taką potworność:

struct bar; 

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

struct bar 
{ 
    foo aFoo; 
}; 

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

Jak widzimy, ptr jest wskaźnik do członka foo (foo::*, czytania od prawej do lewej), w przypadku, gdy członek jest sam wskaźnik do elementu bar (bar::*), gdzie ten element jest int.

Jak bym użyć int foo bar :: * :: *

nie, mam nadzieję, że Ty! Ale jeśli jesteś pod wpływem przymusu, spróbuj tego!

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

Nie jestem pewien, czy rozumiem, jak to jest przetwarzane, ale to nie byłby pierwszy raz, gdy nie rozumiem, jak analizowane jest C++. – zneak

+0

@zneak życzę sobie, aby udało się uzyskać komentarz. – Jeremy

+5

+1 dla "Nie, mam nadzieję!". –

19

To byłby wskaźnik do elementu danych, który jest sam wskaźnik do członka danych (int członek bar).

Nie pytaj mnie, co to jest rzeczywiście przydatna - moja głowa kręci się trochę :)

EDIT: Tutaj jest pełna przykładów, jak to działa:

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

wyjściowa jest, Oczywiście, 42.

może dostać nawet więcej zabawy - oto niektóre wskaźniki do funkcji składowych, które zwracają wskaźniki do funkcji składowych:

#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

Właśnie wtedy, gdy wydawało mi się, że zaczynam rozumieć niektóre z tych stwierdzeń, twój drugi przykład zniszczył moje nadzieje. –

+0

Rozumiem, dlaczego drugi przykład zniszczył twoje nadzieje :). – hlide

+0

1) pasek definiuje metodę o nazwie f_bar mającą argument liczby całkowitej i zwracającą liczbę całkowitą. Łatwe do zrozumienia do tej pory. 2) foo definiuje metodę f_foo bez argumentów i zwracającą wskaźnik do metody paska mającej argument liczby całkowitej i zwracającą liczbę całkowitą. Mniej czytelne, przyznaję. 3) main first definiuje lokalny "ptr" jako wskaźnik do metody foo nie posiadającej argumentu i zwracający wskaźnik do metody pręta posiadającej argument liczby całkowitej i zwracający liczbę całkowitą i przypisującą ją f_foo metoda foo. O, tak długo to opisuję! kontynuować! – hlide

13

Niech analizowania deklaracji int bar::*foo::*ptr;.

§8.3.3 [dcl.mptr]/P1:

W deklaracji T D gdzie D ma postać

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

i zagnieżdżona nazwa-specyfikator oznacza klasę i typ identyfikatora w deklaracji T D1 jest "pochodny-deklarator-typ-lista-listaT", następnie typ identyfikatora z D jest „pochodzą-declarator-type-listacv-kwalifikatora nast wskaźnik do członka klasy nested-name-specyfikującego typu T”.

  • Etap 1: Jest to zgłoszenie z powyższych postaci, w którym T = INT zagnieżdżone nazwa-specyfikator = bar:: i D1 = foo::* ptr. Najpierw przyjrzymy się deklaracji T D1 lub int foo::* ptr.

  • Krok 2: Ponownie stosujemy tę samą zasadę. int foo::* ptr jest deklaracją powyższego formularza, gdzie T = int, określnik nazwy zagnieżdżonej = foo:: i D1 = ptr. Oczywiście typ identyfikatora w int ptr to "int", więc typem identyfikatora ptr w deklaracji int foo::* ptr jest "wskaźnik do elementu klasy foo typu int".

  • Krok 3. Wracamy do pierwotnej deklaracji; typ identyfikatora w T D1 (int foo::* ptr) jest „wskaźnik członka klasy foo typu int” na etapie 2, tak pochodzące-declarator typu lista- jest „wskaźnik członka klasy foo typu”. Substytucja informuje nas, że deklaracja ta deklaruje, że ptr jest "wskaźnikiem dla elementu klasy foo wskaźnika typu dla elementu klasy bar typu int".

Mam nadzieję, że nigdy nie będziesz musiał używać takiej potworności.

+0

Czy były różnice, jeśli zadeklarowaliśmy 'int bar :: * (foo :: * ptr) = nullptr;' zamiast? –

+0

@DmitryFucintv Nie, powiązanie jest takie samo. –

3

Jeśli ktoś się zastanawia, nie można utworzyć wskaźnika do członka, który znajduje się w wielu warstwach. Powodem tego jest to, że wszystkie wskaźniki do członków są w rzeczywistości bardziej skomplikowane niż to, na co patrzą na pierwszy rzut oka; nie zawierają po prostu określonego przesunięcia dla tego konkretnego członka.

Korzystanie z prostego offsetu nie działa z powodu dziedziczenia wirtualnego i upodobań; w zasadzie może się zdarzyć, że nawet w jednym typie przesunięcia określonego pola różnią się między instancjami, a zatem rozdzielczość w trybie natychmiast po zmianie musi być wykonana w środowisku wykonawczym. Głównie wynika to z faktu, że standard nie określa, w jaki sposób może działać wewnętrzny układ dla typów innych niż POD, więc nie ma sposobu, aby to działało statycznie.

W takim przypadku wykonanie głębokiej rozdzielczości dwupoziomowej nie może zostać wykonane przy użyciu zwykłego wskaźnika do elementu, ale konieczne jest wygenerowanie wskaźnika w taki sposób, aby zawierał podwójną informację o jednym głębokim wskaźniku do członka.

Wyobrażam sobie, że skoro wskaźniki od członków do siebie nie są powszechne, nie ma potrzeby tworzenia składni, która umożliwiałaby ustawianie wielowarstwowych głębokich elementów, gdy nadal można używać wielu wskaźników w celu uzyskania tego samego wyniku.

1

Po pierwsze, aby pomóc "czytelność" można użyć nawiasów (kompilacja będzie działać):

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; 

pamiętać, że

int (bar :: * cokolwiek)

jest odpowiednikiem

int (* cokolwiek)

z ograniczeniem dotyczącym członkostwa w barze.

chodzi o

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

, jest to równoważne

int (* (* ptr))

z dwoma ograniczeniami dotyczącymi członkostwa foo i baru.

To tylko wskazówki. Nie sprawdzają, czy bar lub foo rzeczywiście mają zgodny element, ponieważ uniemożliwiłoby to użycie deklaracji w przód paska klasy, a pasek klasy nie sprawdza, czy inne klasy odnoszą się do jej członków za pomocą wskaźników. Poza tym, możesz również potrzebować skierować klasę nieprzezroczystą (to znaczy mieć pasek klasy zdefiniowany w oddzielnej jednostce).

Co z użytecznością? może dla refleksji w C++ jako sposób na ustawienie/pobranie wartości członka klasy przez opakowanie klasy?

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

Oczywiście przykład ten nie wykazuje szczególnego wykorzystania podwójnym wskaźnik zarejestrował bo nie widzę w prosty sposób zilustrować go tutaj lub dobry powód do tego koncepcyjnie mówienia.

Powiązane problemy