Przeciążanie operatorów dostępu do członków ->,. *

132

Rozumiem najwięcej przeciążanie operatora, z wyjątkiem operatorów dostępowych członkiem ->, .*, ->*itd.

W szczególności, co jest przekazywane do tych funkcji operatora, a co powinno zostać zwrócone?

W jaki sposób operator funkcjonuje (np. operator->(...)) Wie, do którego elementu członkowskiego się odwołuje? Czy to może wiedzieć? Czy to w ogóle musi wiedzieć?

Wreszcie, czy są jakieś względy, które należy wziąć pod uwagę? Na przykład, gdy przeciążasz coś takiego operator[], generalnie będziesz potrzebować zarówno wersji stałej, jak i innej. Czy operatorzy dostępu do elementów członkowskich wymagają wersji const i non-const?

Bingo
źródło
1
Uważam, że powyższy C ++ - FAQ dotyczy wszystkich pytań zadanych w powyższym pytaniu.
Alok Zapisz
consta constwersje operator->nie są wymagane , ale podanie obu może być przydatne.
Fred Foo
1
Zobacz też: yosefk.com/c++fqa/operator.html
György Andrasek
9
@Als: Często zadawane pytania nie wyjaśniają, jak przeciążać ->*i .*. W rzeczywistości nawet o nich nie wspomina! Wydaje mi się, że rzadko pojawiają się w FAQ, ale chętnie podam link do tego pytania z FAQ. Proszę nie zamykać tego jako oszustwa z FAQ!
sbi
@sbi, po prostu nie udało mi się znaleźć linku do tego pytania z twojego (niesamowitego) FAQ i zadałem zduplikowane pytanie. Czy mógłbyś uczynić to bardziej oczywistym? (przepraszam, jeśli to już oczywiste).
P i

Odpowiedzi:

148

->

To jedyny naprawdę trudny. Musi to być niestatyczna funkcja składowa i nie pobiera żadnych argumentów. Wartość zwracana jest używana do wyszukiwania elementu członkowskiego.

Jeśli zwracana wartość jest innym obiektem typu klasy, a nie wskaźnikiem, to kolejne wyszukiwanie elementu członkowskiego jest również obsługiwane przez operator->funkcję. Nazywa się to „zachowaniem drążenia w dół”. Język łączy ze sobą operator->wywołania, aż ostatnia zwróci wskaźnik.

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

Ten jest podstępny tylko dlatego, że nie ma w nim nic specjalnego. Wersja bez przeciążenia wymaga obiektu wskaźnika do typu klasy po lewej stronie i obiektu wskaźnika do typu elementu członkowskiego po prawej stronie. Ale kiedy go przeładujesz, możesz wziąć dowolne argumenty i zwrócić wszystko, co chcesz. Nie musi nawet być członkiem niestatycznym.

Innymi słowy, ten jest po prostu normalnym operatorem, jak +, -i /. Zobacz także: Czy wolny operator -> * przeciąża zło?

.* i .

Nie można ich przeciążać. Istnieje już wbudowane znaczenie, gdy lewa strona jest typu klasowego. Być może miałoby to trochę sensu, gdyby można było je zdefiniować dla wskaźnika po lewej stronie, ale komitet projektujący język zdecydował, że byłoby to bardziej zagmatwane niż przydatne.

Przeciążenia ->, ->*, ., i .*może wypełnić tylko w przypadku, gdy wyrażenie będzie niezdefiniowana, to nigdy nie może zmienić znaczenie wyrażenia, które byłyby ważne bez przeciążenia.

Potatoswatter
źródło
2
Twoje ostatnie stwierdzenie nie jest do końca prawdziwe. Na przykład możesz przeciążać newoperatora, nawet jeśli jest on prawidłowy, nawet jeśli nie jest przeciążony.
Matt,
6
@Matt well, newjest zawsze przeciążony lub reguły przeciążania tak naprawdę go nie dotyczą (13.5 / 5: Funkcje alokacji i zwalniania alokacji, operator new, operator new [], operator delete i operator delete [], są opisane całkowicie w 3.7 0,4. atrybuty i ograniczenia znajdują się w dalszej części tego podrozdziału nie stosują się do nich, chyba że wyraźnie zaznaczono w 3.7.4). Ale przeciążenia jednoskładnikowa &lub binarny &&, ||lub ,, lub dodając przeciążeń operator=lub przeciążenia czegokolwiek dla unscoped typ wyliczenia, może zmienić znaczenie wyrażenia. Wyjaśniłem oświadczenie, dzięki!
Potatoswatter
41

Operator -> jest wyjątkowy.

„Ma dodatkowe, nietypowe ograniczenia: musi zwrócić obiekt (lub odniesienie do obiektu), który również ma operator wyłuskiwania wskaźnika, lub musi zwrócić wskaźnik, który może być użyty do wybrania tego, na co wskazuje strzałka operatora wyłuskiwania wskaźnika. " Bruce Eckel: Thinking CPP Vol-One: operator->

Dodatkowa funkcjonalność jest zapewniona dla wygody, więc nie musisz dzwonić

a->->func();

Możesz po prostu zrobić:

a->func();

To sprawia, że ​​operator -> różni się od innych przeciążeń operatorów.

Totonga
źródło
3
Ta odpowiedź zasługuje na więcej uznania, możesz pobrać książkę Eckela z tego łącza, a informacje znajdują się w rozdziale 12 tomu pierwszego.
P i
26

Nie można przeciążyć dostępu do członków .(tj. Drugiej części tego, co ->robi). Możesz jednak przeciążać jednoargumentowy operator wyłuskiwania* (czyli pierwszą część tego, co ->robi).

Operator C ++ ->jest w zasadzie połączeniem dwóch kroków i jest to jasne, jeśli uważasz, że x->yjest to równoważne (*x).y. C ++ pozwala dostosować, co zrobić z (*x)częścią, gdy xjest instancją Twojej klasy.

Semantyka ->przeciążania jest nieco dziwna, ponieważ C ++ umożliwia albo zwrócenie zwykłego wskaźnika (który zostanie użyty do znalezienia wskazanego obiektu), albo zwrócenie instancji innej klasy, jeśli ta klasa również udostępnia ->operator. W tym drugim przypadku wyszukiwanie wyłuskowanego obiektu jest kontynuowane z tej nowej instancji.

6502
źródło
2
Świetne wyjaśnienie! Myślę, że oznacza to to samo dla ->*, ponieważ jest równoważne z formą (*x).*?
Bingo
10

->Operator nie wie co jest członek wskazał, że tylko zapewnia obiektu do przeprowadzenia rzeczywistego dostępu członkowskich w sprawie.

Ponadto nie widzę powodu, dla którego nie można zapewnić wersji const i non-const.

John Chadwick
źródło
7

Kiedy przeciążasz operator -> () (żadne argumenty nie są tutaj przekazywane), kompilator w rzeczywistości wywołuje -> rekurencyjnie, dopóki nie zwróci rzeczywistego wskaźnika do typu. Następnie używa prawidłowego elementu członkowskiego / metody.

Jest to przydatne na przykład do tworzenia inteligentnej klasy wskaźnika, która hermetyzuje rzeczywisty wskaźnik. Wywoływany jest przeciążony operator->, robi to, co robi (np. Blokowanie w celu zapewnienia bezpieczeństwa wątków), zwraca wewnętrzny wskaźnik, a następnie kompilator wywołuje -> dla tego wewnętrznego wskaźnika.

Jeśli chodzi o stałość - odpowiedziano na to w komentarzach i innych odpowiedziach (możesz i powinieneś podać oba).

Asaf
źródło