czwartek, 20 stycznia 2011

Referencja NULL w C++

Jedną z głównych różnic między wskaźnikami a referencjami w C++ jest fakt, iż referencja nie może wskazywać na NULL. Ta, jasne.

Faktycznie, standard ISO 14882 stwierdza (rozdział 8.3.2, punkt 4):
"A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior."

Ale standard sobie, a implementacje sobie, więc to "only way" wcale nie jest takie only :) Podwińcie rękawy i zasłońcie oczy, bo pięknie to tu nie będzie ;)

W klasycznym podejściu można by próbować uzyskać referencję do NULL, dokonując derefencji takiego wskaźnika:

int* p = NULL;
int& r = *p;


Od razu widać, że to nie pójdzie, NullPointerException gwarantowany :)

My zastosujemy inny trik, który wynika ze szczegółów implementacji referencji w większości kompilatorów. Mianowicie "pod spodem" są one implementowane z użyciem najzwyklejszych wskaźników. Oznacza to, że dwie następujące struktury:

struct APtr
{
  int* p_a;
};

struct ARef
{
  int& a;
};


jeżeli chodzi o "bitowy" wygląd są identyczne - mimo że jedna zawiera wskaźnik na int, a druga referencję do tego typu.

Uzbrojeni w tą wiedzę przyszykujmy więc APtr ze wskaźnikiem na NULL:

APtr aptr;
aptr.p_a = NULL;


Niestety zwykłe rzutowanie z jednego typu na drugi nie zadziała:

ARef aref((ARef)aptr);

Ale możemy spróbować zrzutować miedzy sobą wskaźniki na oba typy z użyciem reinterpret_cast:

ARef aref(*reinterpret_cast<ARef*>(&aptr));

Czyli najpierw rzutując uzyskujemy wskaźnik na ARef ze wskaźnika na typ APtr, a następnie z pomocą operatora * uzyskujemy wskazywany obiekt (czyli aptr) jako obiekt klasy ARef, który to przekazujemy do konstruktora kopiującego. I...

Sukces! Tzn. brak błędów kompilacji. To teraz wystarczy sprawdzić, czy faktycznie aref.a zawiera referencję NULL:

int& ref = aref.a;
std::cout << "Adres referencji to: " << &ref << std::endl;


A efekt to (w Visual C++ 2008 Express):

Adres referencji to: 00000000

Czyli nie ma rzeczy niemożliwych ;) I tym optymistycznym akcentem...

Cały kody źródłowy to zwykłego przeklejenia i sprawdzenia samemu w swoim ulubionym kompilatorze:

#include <iostream>

using namespace std;

struct APtr
{
  int* p_a;
};

struct ARef
{
  int& a;
};


int main()
{
  APtr aptr;
  aptr.p_a = NULL;
  ARef aref(*reinterpret_cast<ARef*>(&aptr));
  int& ref = aref.a;
  std::cout << "Adres referencji to: " << &ref << std::endl;
  return 0;
}


All credit shall go to JKop:
http://www.velocityreviews.com/forums/showpost.php?p=1512585&postcount=7

1 komentarz:

  1. Kumle CPluPlusowcy mówili, że jak przeprowadzają rozmowę kwalifikacyjną i się pytają, jaka jest różnica pomiędzy wskaźnikiem a referencją, to jedną z częstszych odpowiedzi jest to, że referencja nie może być null - wtedy to uświadamiają takiego delikwenta, że jednak może być :)

    OdpowiedzUsuń