Dlaczego operator strzałki w C ++ nie jest tylko aliasem *.?

18

W c ++ operator * może być przeciążony, na przykład iteratorem, ale operator strzałki (->) (. *) Nie działa z klasami, które przeciążają operatora *. Wyobrażam sobie, że preprocesor mógłby z łatwością zastąpić wszystkie wystąpienia -> znakiem (* left) .right, i dzięki temu iteratory byłyby łatwiejsze do zaimplementowania. czy istnieje praktyczny powód, dla którego -> jest inny, czy jest to po prostu specyfika języka / projektantów?

Jakob Weisblat
źródło

Odpowiedzi:

16

Zasada, która foo->barjest równa, (*foo).bardotyczy tylko wbudowanych operatorów.

Unary operator *nie zawsze ma semantykę dereferencji wskaźnika. Mógłbym stworzyć bibliotekę, w której oznacza to transpozycję macierzy, zero lub więcej dopasowań parsera lub cokolwiek innego.

Sprawiłoby to, że język byłby bardziej kłopotliwy, gdyby coś, co przeciąża jednoargumentowo operator *, nagle zyskałoby coś, o operator ->co nie prosiłeś, z semantyką, która może nie mieć sensu.

operator -> jest osobno przeciążalny, więc jeśli chcesz, możesz przeciążyć jeden przy minimalnym wysiłku.

Zauważ też, że takie przeciążenie miałoby pewne dość interesujące właściwości, takie jak automatyczne łączenie operator ->wywołań, dopóki jeden w łańcuchu nie zwróci surowego wskaźnika. Jest to bardzo przydatne w przypadku inteligentnych wskaźników i innych typów proxy.

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <iostream>
#include <ostream>

struct Foo
{
    boost::shared_ptr<std::string> operator -> () const
    {
        return boost::make_shared<std::string>("trololo");
    }
};

int main()
{
    Foo foo;
    std::cerr << foo->size() << std::endl;
}
Lars Viklund
źródło
Co ilustruje twój przykład? Czy zwracasz inteligentny wskaźnik do łańcucha i w jakiś sposób wyświetlasz rozmiar? Jestem zmieszany.
Trevor Hickey,
2
Ilustruje ostatni akapit mojej odpowiedzi, w jaki sposób korzystanie z ->łańcuchów operatora, dopóki nie uzyska surowego wskaźnika do czegoś, dereferencje i uzyskiwanie dostępu do członka. Jeśli operator -> nie utworzył łańcucha, przykład byłby źle sformułowany, ponieważ share_ptr nie jest surowym wskaźnikiem.
Lars Viklund,
@ LarsViklund: Twoja odpowiedź ma problem: powiedziałeś „operator-> ... automatycznie łączy operatora-> połączenia, dopóki jeden w łańcuchu nie zwróci surowego wskaźnika”. To nie jest poprawne - użycie A->Błańcuchów składniowych co najwyżej 1 dodatkowe wywołanie. To, co faktycznie robi składnia binarna C ++ ->, nie wywołuje opeartor->bezpośrednio obiektu - zamiast tego sprawdza typ Ai sprawdza, czy jest to surowy wskaźnik. Jeśli tak, to ->usuwa go z pamięci i wykonuje Bna nim, w przeciwnym razie wywołuje obiekt operator->, usuwa z pamięci wynik (albo używając natywnego wskaźnika raw, albo innego, operator->a następnie wykonuje Bna wyniku
Guss 14.04.13
@Guss: Nie mogę znaleźć żadnego rozdziału i wersetu dla twojego roszczenia ani odtworzyć go w kompilatorze. C ++ 11 13.5.6 / 1 wskazuje, że jeżeli istnieje odpowiednie przeciążenie, x->mnależy je interpretować jako (x.operator->())->m. Jeśli LHS jest czymś, co operator->ponownie ma odpowiednie przeciążenie , proces ten powtarza się, dopóki nie pojawi się zwykły (*x).mefekt 5.2.5 / 2.
Lars Viklund,
8

„Język programowania C ++” opisuje fakt, że operatory te są różne, aby mogły być, ale także mówi:

Jeśli podasz więcej niż jeden z tych operatorów, rozsądne może być podanie równoważności, tak jak rozsądnie jest zapewnić ++xi x+=1mieć taki sam efekt jak x=x+1dla prostej zmiennejx jakiejś klasy, jeśli ++, + =, = i + są zapewnione.

Wydaje się więc, że projektanci językowe przewidziane osobne punkty przeciążenia, ponieważ może chce przeładowywać je inaczej, niż przy założeniu, że zawsze chce im się tym samym.


źródło
7

Zasadniczo C ++ jest zaprojektowany tak, aby sprzyjał elastyczności, więc przeciążenia *i ->są osobne. Chociaż jest to dość niezwykłe, jeśli chcesz tego wystarczająco mocno, możesz napisać te przeciążenia, aby robić zupełnie różne rzeczy (np. Może mieć sens dla języka specyficznego dla domeny zaimplementowanego w C ++).

To powiedziawszy, iteratory zrobić obsługują użycie. W starożytnych implementacjach możesz znaleźć bibliotekę, która wymaga (*iter).whateverzamiast tego iter->whatever, ale jeśli tak, jest to błąd w implementacji, a nie cecha charakterystyczna języka. Biorąc pod uwagę ilość pracy związanej z implementacją wszystkich standardowych kontenerów / algorytmów / iteratorów, nie jest zaskakujące, że niektóre wczesne wydania były nieco niekompletne, ale tak naprawdę nigdy nie były przeznaczone.

Jerry Coffin
źródło
Nie zdawałem sobie sprawy, że zaimplementowane standardowe kontenery biblioteczne ->, lub że był przeciążony.
Jakob Weisblat
3
C ++ 03 24.1 / 1 wymaga, aby każdy iterator, który (*i).mjest poprawny, obsługiwał i->mtę samą semantykę.
Lars Viklund,