Blog
Referencja kontra Wskaźnik
Wojciech Stróżyński 23.10.2018
Sprawa wydaje się być błaha. Każdy wie do czego służy wskaźnik, a do czego referencja. Ale czy aby na pewno znamy różnice między nimi i wiemy gdzie które z nich stosować? Przekonajmy się.
Na początek wskażmy różnice między tymi dwoma:
Cecha | Wskaźnik | Referencja |
---|---|---|
Brak przypisania | Możliwy - nullptr | Niemożliwy. Referencja musi odnosić się do konkretnego obiektu |
Ponowne przypisanie | Wskaźnik przyjmować różne wartości, jeśli tylko nie został zainicjowany jako const int * const ptr_i = &i | Raz przypisana referencja nie może ulec zmianie - zawsze odnosi się do tego samego obiektu |
&zmienna | Zwraca adres tego wskaźnika | Zwraca do adres to obiektu, do którego odnosi się referencja |
sizeof(zmienna) | Zwraca rozmiar wskaźnika | Zwraca rozmiar obiektu do którego się odnosi |
Pomimo faktu, że sizeof() wywołany z referencją jako argumentem zwraca wielkość obiektu do którego referencja się odnosi, to w praktyce większość kompilatorów (jeśli nie wszystkie) implementuje referencję jako wskaźnik. Tym samym referencja fizycznie w pamięci zajmuje dokładnie tyle samo miejsca co wskaźnik… czyli ile? Tego ile waży wskaźnik dowiecie się w osobnym artykule. Na razie wróćmy jednak do tematu różnic między referencją a wskaźnikiem.
A skoro referencja jest implementowana jako fizyczna zmienna, to trzeba mieć więc na uwadze fakt, że ref_i w poniższym przykładzie
int &ref_i = i; int *ptr_i = &i;
nie zostanie kompletnie usunięta w wyniku optymalizacji kompilatora, a zmienna ref_i będzie fizycznie zajmowała dokładnie tyle samo miejsca w pamięci co wskaźnik ptr_i.
To znaczy MOŻE nie zostać usunięta, bo to co kompilator robi z naszym kodem może nas nieźle zdziwić. W praktyce może się okazać że z naszego kodu w ogóle zostanie tylko zwrócenie wartości z funkcji main.
Kiedy referencja, a kiedy wskaźnik?
Odpowiedź na to pytanie nie jest jednoznaczna, bo te dwie koncepcje da się wykorzystywać zamiennie w większości przypadków. Ale istnieje jedna ogólna zasada – używaj referencji kiedy tylko jest to możliwe. Dopiero jeśli się nie da, użyj pointera. Gdzie się nie da?
-
- Obiekt jest opcjonalny – potrzeba użycia nullptr. Alternatywie możemy do tego wykorzystać boost::optional (wprowadzony do standardu od C++17).
- Potrzeba korzystania z biblioteki napisanej w C (lub C-style).
- Przechowywanie referencji na obiekty w kontenerach STLowych – jest to niemożliwe, że względu na to, że obiekty przechowywane w wektorze muszą być „przypisywalne”. Const int również nie wchodzi w grę. Do obejścia problemu można wykorzystać std::reference_wrapper, lub zwykły wskaźnik.
- Musisz operować na wskaźnikach do pamięci – praca blisko sprzętu. Wtedy jednak zaleca się maksymalnie ograniczyć ich wykorzystanie i utworzenie dobrej abstrakcji, która na zewnątrz nie będzie już wystawiać api wymagającego używania czystych wskaźników.
- Zgodnie ze słowami Herba Suttera jeśli musimy przekazać do funkcji obiekt trzymany w unique_ptr czy shared_ptr ale nie chcemy oddawać jego posiadania, powinniśmy przekazać go jako wskaźnik. Tym samym utrzymujemy konwencję, w której wskaźnik może być null lub wskazywać na obiekt danego typu. Programiści zbyt często przekazują w tym miejscu referencję do całego smart pointera. Jest to jednak niezalecane rozwiązanie. Sygnatura funkcji zawierająca referencję na smart pointer jest zarezerwowana dla funkcji, która modyfikuje sam smart pointer, a nie to, co ten smart pointer przechowuje.
Na dobry początek należałoby się jednak zastanowić, czy przekazanie wskaźnika do obiektu jako argument funkcji jest w ogóle konieczne w naszym przypadku. Może da się to zaprojektować inaczej?
Uprzedzając komentarze
Jest możliwe utworzenie referencji, która jest pusta. Robi się to tak:
int *p = NULL;
int &r = *p;
r = 1;
W powyższym przykładzie jednak już dereferencjonowanie wskaźnika o wartości NULL samo w sobie jest undefined behavior. Zainicjowanie referencji czymś co powoduje zachowanie niezdefiniowane dalej jest błędne. Powyższy kod mimo, że może się kompilować bez błędu, jest absolutnie niepoprawny, a za argumentowanie nim czegokolwiek powinno się karać szklaneczką… co ja piszę?! Szklanką pu-erha!