To jest dziwactwo / funkcja w C ++. Chociaż nie myślimy o referencjach jako o typach, w rzeczywistości „siedzą” one w systemie typów. Chociaż wydaje się to niezręczne (biorąc pod uwagę, że gdy używane są odwołania, semantyka odwołań występuje automatycznie, a odniesienie „znika z drogi”), istnieją pewne uzasadnione powody, dla których odniesienia są modelowane w systemie typów zamiast jako oddzielny atrybut poza rodzaj.
Po pierwsze, rozważmy, że nie każdy atrybut zadeklarowanej nazwy musi znajdować się w systemie typów. Z języka C mamy „klasę pamięci” i „powiązanie”. Nazwę można wprowadzić jako extern const int ri
, gdzie extern
oznacza statyczną klasę pamięci i obecność powiązania. Ten typ jest sprawiedliwy const int
.
C ++ oczywiście obejmuje pogląd, że wyrażenia mają atrybuty, które są poza systemem typów. Język ma teraz koncepcję „klasy wartości”, która jest próbą uporządkowania rosnącej liczby atrybutów innych niż typowe, które może wykazywać wyrażenie.
Jednak odniesienia są typami. Czemu?
Kiedyś wyjaśniono w samouczkach C ++, że deklaracja taka jak const int &ri
wprowadzona ri
jako posiadająca typ const int
, ale semantyka odwołań. Ta semantyka odniesienia nie była typem; był to po prostu rodzaj atrybutu wskazującego na niezwykły związek między nazwą a miejscem przechowywania. Ponadto fakt, że odwołania nie są typami, został wykorzystany do uzasadnienia, dlaczego nie można konstruować typów na podstawie odwołań, mimo że pozwala na to składnia konstrukcji typu. Na przykład tablice lub wskaźniki do odwołań nie są możliwe:const int &ari[5]
iconst int &*pri
.
Ale w rzeczywistości odwołania są typami i dlatego decltype(ri)
pobiera węzeł typu referencyjnego, który jest niekwalifikowany. Musisz zejść poza ten węzeł w drzewie typów, aby dostać się do typu bazowego zremove_reference
.
Kiedy używasz ri
, odniesienie jest rozwiązywane w sposób przezroczysty, więc ri
„wygląda i czuje się jak i
” i można je nazwać „aliasem”. Jednak w systemie typów ri
w rzeczywistości istnieje typ, który jest „ odniesieniem do const int
”.
Dlaczego istnieją typy referencyjne?
Weź pod uwagę, że gdyby odwołania nie były typami, wówczas te funkcje zostałyby uznane za mające ten sam typ:
void foo(int);
void foo(int &);
Po prostu nie może tak być z powodów, które są dość oczywiste. Gdyby miały ten sam typ, oznacza to, że każda deklaracja byłaby odpowiednia dla którejkolwiek z definicji, a zatem każda (int)
funkcja musiałaby być podejrzewana o przyjmowanie referencji.
Podobnie, gdyby referencje nie były typami, te dwie deklaracje klas byłyby równoważne:
class foo {
int m;
};
class foo {
int &m;
};
Byłoby poprawne, gdyby jedna jednostka tłumaczeniowa używała jednej deklaracji, a inna jednostka tłumaczeniowa w tym samym programie używała drugiej deklaracji.
Faktem jest, że odniesienie implikuje różnicę w implementacji i nie można go oddzielić od typu, ponieważ typ w C ++ ma do czynienia z implementacją encji: jej „układ” w bitach, że tak powiem. Jeśli dwie funkcje mają ten sam typ, można je wywołać z tymi samymi konwencjami wywoływania binarnego: ABI jest taki sam. Jeśli dwie struktury lub klasy mają ten sam typ, ich układ jest taki sam, jak również semantyka dostępu do wszystkich członków. Obecność odniesień zmienia te aspekty typów, więc włączenie ich do systemu typów jest prostą decyzją projektową. (Należy jednak zwrócić uwagę na kontrargument: może to być element członkowski struktury / klasy static
, który również zmienia reprezentację; ale to nie jest typ!)
Zatem odniesienia znajdują się w systemie typów jako „obywatele drugiej kategorii” (podobnie jak funkcje i tablice w ISO C). Są pewne rzeczy, których nie możemy „zrobić” z odniesieniami, takie jak deklarowanie wskaźników do referencji lub ich tablic. Ale to nie znaczy, że nie są typami. Po prostu nie są typami w sensie, który ma sens.
Nie wszystkie te ograniczenia drugiej klasy są niezbędne. Biorąc pod uwagę, że istnieją struktury odwołań, mogą istnieć tablice odwołań! Na przykład
int x = 0, y = 0;
int &ar[2] = { x, y };
To po prostu nie jest zaimplementowane w C ++, to wszystko. Wskaźniki do odwołań w ogóle nie mają jednak sensu, ponieważ wskaźnik podniesiony z odniesienia po prostu kieruje się do obiektu, do którego się odwołuje. Prawdopodobnym powodem braku tablic odniesień jest to, że ludzie C ++ uważają tablice za rodzaj funkcji niskiego poziomu dziedziczonej po C, która jest zepsuta na wiele sposobów, które są nieodwracalne i nie chcą dotykać tablic jako podstawa do czegoś nowego. Jednak istnienie tablic odniesień stanowiłoby jasny przykład tego, jak odwołania muszą być typami.
Nie-const
-qualifiable typy: znaleziono w ISO C90, też!
Niektóre odpowiedzi sugerują, że odwołania nie wymagają const
kwalifikatora. Jest to raczej czerwony śledź, ponieważ deklaracja const int &ri = i
nawet nie próbuje utworzyć const
referencji kwalifikowanej: jest to odwołanie do typu z kwalifikacją const (który sam w sobie nie jest const
). Tak jak const in *ri
deklaruje wskaźnik do czegoś const
, ale ten wskaźnik sam nie jest const
.
To powiedziawszy, prawdą jest, że referencje nie mogą const
same nosić kwalifikatora.
Jednak nie jest to takie dziwne. Nawet w języku ISO C 90 nie wszystkie typy mogą być const
. Mianowicie, tablic nie może.
Po pierwsze, nie istnieje składnia deklarowania tablicy const: int a const [42]
jest błędna.
Jednak to, co stara się zrobić powyższa deklaracja, można wyrazić za pośrednictwem pośrednika typedef
:
typedef int array_t[42];
const array_t a;
Ale to nie robi tego, na co wygląda. W tej deklaracji nie kwalifikuje się to, a
co jest const
kwalifikowane, ale elementy! To znaczy, a[0]
jest a const int
, ale a
jest po prostu „tablicą int”. W konsekwencji nie wymaga to diagnostyki:
int *p = a;
To robi:
a[0] = 1;
Ponownie, to podkreśla ideę, że odwołania są w pewnym sensie „drugą klasą” w systemie typów, podobnie jak tablice.
Zwróć uwagę, że analogia jest jeszcze głębsza, ponieważ tablice mają również „niewidoczne zachowanie konwersji”, takie jak odwołania. Bez konieczności używania przez programistę żadnego jawnego operatora, identyfikator a
automatycznie zamienia się we int *
wskaźnik, tak jakby &a[0]
zostało użyte wyrażenie . Jest to analogiczne do tego, jak odniesienie ri
, gdy używamy go jako wyrażenia pierwotnego, w magiczny sposób oznacza przedmiot, i
z którym jest związane. To tylko kolejny „rozpad”, taki jak „rozpad tablicy na wskaźnik”.
I tak jak nie możemy dać się zmylić faktem, że „tablica do wskaźnika” rozpada się na błędne myślenie, że „tablice są tylko wskaźnikami w C i C ++”, tak samo nie powinniśmy myśleć, że odwołania to tylko aliasy, które nie mają własnego typu.
Gdy decltype(ri)
pomija zwykłą konwersję odwołania do obiektu referencyjnego, nie różni się to tak bardzo od sizeof a
pomijania konwersji tablicy na wskaźnik i operowania na samym typie tablicy w celu obliczenia jej rozmiaru.
boolalpha(cout)
bardzo nietypowy. Możeszstd::cout << boolalpha
zamiast tego zrobić .