Referencja [lub bardziej po polsku: odniesienie - przyp. tłum.] jest aliasem (czyli alternatywną nazwą) dla obiektu.
Referencje często są wykorzystywane do przekazywania przy ich pomocy parametrów dla funkcji.
W powyższym przykładzie i oraz j są aliasami dla zmiennych x i y z funkcji main. Inaczej mówiąc i jest zmienną x nie wskaźnikiem do x, ani kopią x, tylko po prostu zmienną x. Cokolwiek każesz zrobić z i to stanie się to również z x-em i odwrotnie.
OK. Tyle powinieneś wiedzieć o referencjach jako programista. Teraz, ryzykując możliwość tego, że trochę Ci zamieszam w temacie, postaram się wyjaśnić dokładniej czym są te referencje. Na poziomie kodu maszynowego referencja i do obiektu x nie jest niczym innym, jak adresem do obiektu x - czyli wskaźnikiem. Jednak gdy programista pisze i++, to kompilator generuje kod zwiększający o jeden wartość zmiennej x - a nie zwiększający wskaźnik. Adres, którego kompilator używa do znalezienia zmiennej x nie jest zmieniany. Programista piszący w języku C może traktować referencję jak wskaźnik w którym (1) operator & zwraca adres wskazywanego obiektu, a nie adres pod którym znajduje się referencja i (2) wyeliminowano użycie operatora *. Innymi słowy, programista obeznany z C może traktować i jako makro dla *p, gdzie p jest wskaźnikiem do x (przykład: i++ można zamienić na (*p)++, a i = 7 na *p = 7). [Z kolei "&i" odpowiada p - przyp. tłum.].
Ważna uwaga: Chociaż referencje są często implementowane w asemblerze wykorzystywanym przez kompilator jako adres do obiektu, proszę - nie myśl o referencji jak o śmiesznie wyglądającym wskaźniku. Referencja jest obiektem. To nie jest żaden wskaźnik do obiektu ani kopia tego obiektu. To jest obiekt.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Zmieni się wtedy stan referenta (obiektu, do którego odnosi się referencja).
Zapamiętaj: referencja jest obiektem do którego się ona odnosi, więc zmieniając referencję zmieniamy stan tego obiektu. W żargonie autorów kompilatorów, referencja jest "l-wartością" (czymś, co może występować po lewej stronie operatora przypisania "=").
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Jeśli funkcja zwraca referencję, wtedy jej wywołanie może się znajdować po lewej stronie operatora przypisania.
Na początku może się to wydawać dziwne. Np. mało kto twierdzi, że wyrażenie f() = 7 ma jakiś sens. Jednakże, jeśli a będzie obiektem klasy Array, większość ludzi zgodzi się z tym, że a[i] = 7 ma sens, nawet jeżeli a[i] jest tak naprawdę wywołaniem funkcji Array::operator[](int).
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Zapis ten powoduje wywołanie metod w kolejności od lewej do prawej. Wygląda to trochę jak "łańcuszek metod", stąd zapis taki nazywany jest w języku angielskim method chaining [tłumacząc dosłownie: "łańcuchowanie metod" - przyp. tłum.].
W podanym przykładzie najpierw zostanie wywołana metoda object.method1(). Zwróci ona jakiś obiekt, który może być referencją do object'a (np. jeśli funkcja method1() kończy się instrukcją return *this;), lub jakimś innym obiektem. Nazwijmy zwrócony obiekt objectB. Następną metodą, która zostanie wywołana, będzie objectB.method2().
Typowym przykładem takiego wywoływania metod jest korzystanie z biblioteki iostream. Np. wywołanie cout << x << y działa dlatego, że funkcja cout << x zwraca obiekt cout [po czym następuje wywołanie cout << y - przyp. tłum.].
"Method chaining" ma także mniej typowe, ale również interesujące, zastosowanie w implementacji nazwanych parametrów (ang. "Named Parameter Idiom").
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Nie ma szans.
Nie można oddzielić referencji od jej referenta.
W odróżnieniu od wskaźnika, gdy referencja zostanie raz przyporządkowana jakiemuś obiektowi, nie można już jej "przestawić" na inny obiekt. Referencja nie jest obiektem samym w sobie (nie ma własnej "tożsamości" - przy pobieraniu adresu referencji dostajemy adres referenta; zapamiętaj: referencja jest swoim referentem).
W tym znaczeniu referencja jest podobna do stałego (const) wskaźnika, takiego jak int* const p (nie mylić ze wskaźnikiem do stałej w stylu const int* p). Pomimo rażącego podobieństwa, proszę nie mylić referencji ze wskaźnikami: nie są one do końca tym samym.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Jeśli jest to tylko możliwe, używaj referencji. Wskaźników używaj tylko gdy jest to konieczne.
Referencje są na ogół częściej stosowane niż wskaźniki, zwłaszcza jeśli nie jest wymagana możliwość "ich przestawiania". Referencje są bardzo użyteczne jako publiczne składowe klas. Przeważnie referencje są widoczne na zewnątrz klasy, a wskaźniki znajdują się wewnątrz niej.
Wyjątkiem są sytuacje, gdy parametrem funkcji lub zwracaną przez nią wartością może być "pusta" referencja. W takim razie najlepiej jest pobrać/zwrócić wskaźnik zawierający adres do obiektu, a w szczególnym przypadku wartość NULL (referencje powinny zawsze stanowić alias do jakiegoś obiektu).
Niektórzy programiści C "starej linii" nie lubią referencji z powodu związanej z nimi semantyki, utrudniającej w wywołaniu funkcji spostrzeżenia, że chcemy umożliwić zmianę zawartości zmiennej. Często jednak, po uzyskaniu pewnego doświadczenia w C++, zdają sobie sprawę, że takie uproszczenie stanowi pewną formę ukrywania zbędnych informacji, będącą raczej zaletą niż wadą referencji. Przydatne jest to np. wtedy, gdy programista ma wyrazić algorytm w języku bardziej odpowiadającemu rozwiązywanemu problemowi, nie ściśle w języku maszyny.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Uchwyt jest obiektem pozwalającym uzyskać dostęp do innego obiektu [wskaźnikiem, indeksem w tablicy obiektów, napisem stanowiącym nazwę obiektu - czymkolwiek, co pozwala na pośredni dostęp do obiektu - przyp. tłum.] ogólnie mówiąc pseudo-wskaźnikiem. Termin ten jest (celowo) wieloznaczny i niejasny.
Wieloznaczność słowa uchwyt jest w pewnych przypadkach zaletą. Np., w czasie wstępnego projektowania programu możesz nie wiedzieć w jaki sposób chcesz się odwoływać do obiektów. Możesz nie miec pewności, co wybrać: zwykłe wskaźniki, referencje, wskaźniki do wskaźników, referencje do wskaźników, liczby całkowite indeksujące tablicę obiektów, napisy (lub inne klucze) według których będzie można przeszukiwać tablicę hash (lub inną strukturę danych), klucze baz danych lub jakąś inną technikę. Jeśli jedyną dla Ciebie pewną rzeczą będzie to, że potrzebujesz coś, co będzie jednoznacznie identyfikowało jakiś obiekt i będzie pozwalało na dostęp do niego, to możesz to na razie nazwać Uchwytem.
Jeśli w pewnym miejscu programu konieczne będzie np. przetworzenie jakiegoś obiektu (nazwijmy go Fred) przez jakąś funkcję, to możesz jej przekazać jako parametr uchwyt do tego obiektu. Uchwytem może być np. napis który zostanie użyty jako klucz w przeszukiwaniu tablicy (np. w std::map<std::string,Fred> lub std::map<std::string,Fred*>), liczba całkowita stanowiąca indeks do tablicy obiektów (np. Fred* array = new Fred[maxNumFreds]), zwykły wskaźnik Fred* lub cokolwiek innego.
Początkujący często stosują zwykłe wskaźniki w roli uchwytów, ale w praktyce wiąże się z tym szereg niebezpieczeństw. Np. co zrobić, jeśli nagle trzeba przemieścić obiekt Fred do innego miejsca w pamięci? Skąd wiadomo, kiedy można bez obaw usunąć obiekt? A jeśli brakuje już miejsca w pamięci i najlepszym sposobem wybrnięcia z tej sytuacji wydaje się chwilowe przeniesienie obiektu na dysk? Dobrym rozwiązaniem jest stosowanie pewnych warstw pośredniczących. Przykładowo, uchwytem może być Fred** - w takim przypadku wskaźnik-do-wskaźnika-do-Freda (czyli uchwyt) nie będzie zmieniany w trakcie działania programu, natomiast gdy zajdzie konieczność przeniesienia Freda do innego miejsca w pamięci to po prostu zmieniona zostanie wartość wskaźnika-do-Freda [sam wskaźnik pozostanie w tym samym miejscu i uchwyt nadal będzie na niego wskazywał - przyp. tłum.]. Ewentualnie jako uchwyt można zastosować liczbę całkowitą oznaczającą indeks w tablicy Fredów wskazujący na konkretnego Freda. I tak dalej.
Istotą sprawy jest to, że możemy używać słowa "uchwyt" wtedy, gdy jeszcze nie wiemy w jaki dokładnie sposób będziemy uzyskiwać dostęp do obiektów.
Innym celem użycia słowa "uchwyt" jest celowe ukrycie niepotrzebnej informacji na temat zastosowanej przez nas metody (czasem w takiej sytuacji używa się terminu "magic cookie", np. "Program przekazuje dalej magic cookie, które pozwala na jednoznaczną identyfikację i odnalezienie odpowiedniego obiektu klasy Fred). Pozwala to na uniknięcie sytuacji, gdy zmiana definicji uchwytu w jednym miejscu pociąga za sobą zmiany w wielu innych miejscach (z ang. "ripple effect"). Np., jeśli dotychczas uchwytem w programie był napis używany do znajdywania obiektu w tablicy i ktoś postanowi, że od tej pory uchwyty będą zwykłymi liczbami całkowitymi, mało kto będzie chętny do zmieniania "zilionów" wierszy kodu. [Jeśli natomiast w obrębie całego kodu będzie się stosować np. nazwę typu "Uchwyt" to wystarczy zmienić definicję tego typu - przyp. tłum.].
Aby jeszcze bardziej uprościć sobie pracę często definiuje się uchwyt jako klasę przeciążającą operatory operator-> i operator* - dzięki temu uchwyt nie tylko służy do podobnych celów jak wskaźnik, ale nawet wygląda jak wskaźnik. W przypadku zmiany implementacji uchwytu wystarczy zmienić definicję tej klasy.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
E-mail the author
[ C++ FAQ Lite
| Spis treści
| Skorowidz
| O autorze
| ©
| Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja polska: 0.1i Jul 12, 2004