Referencja kontra Wskaźnik

utworzone przez | 23 października 2018 | C++, Junior, Regular

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:

CechaWskaźnikReferencja
Brak przypisaniaMożliwy - nullptrNiemożliwy. Referencja musi odnosić się do konkretnego obiektu
Ponowne przypisanieWskaź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
&zmiennaZwraca adres tego wskaźnikaZwraca do adres to obiektu, do którego odnosi się referencja
sizeof(zmienna)Zwraca rozmiar wskaźnikaZwraca 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!

Podziel się postem!