Kiedy w C++ chcemy przekazać do funkcji odwołanie do obiektu (zezwalające na jego modyfikację wewnątrz funkcji), mamy do wyboru dwie metody. Ta alternatywa to posłużenie się wskaźnikiem albo referencją:
Czy istnieje uniwersalna odpowiedź na to, którą wybrać? Chyba nie. Jeśli chodzi o wskaźnik, to za jego użyciem może przemawiać:
NULL
jako domyślnej wartości dla tego parametru w deklaracji funkcji. Nie jest to możliwe dla referencji (w C++).&
, który daje o tym jakąś widoczną wskazówkę (nie tak jasną jak ref
/out
w C#, ale zawsze). Nie jestem też wielkim fanem notacji węgierskiej, lecz w przypadku wskaźników stosowanie przedrostka p
wydaje mi się akurat wskazane i w tym kontekście też zwiększa czytelność wywołania funkcji, wskazując, że przekazywany obiekt (alokowany na stercie) też może się zmienić.
Z kolei referencje mogą się popisać innymi zaletami:
(*pArray)[i]
, zaś przez referencję po prostu jako array[i]
.Widać więc, że jeśli kwestia odwołania pustego nie jest dla nas istotna, to decyzja może być trudna. Ale naturalnie jest tak tylko wtedy, gdy zechcemy się nad takimi sprawami zastanawiać ;]
Bardzo ciekawa notka. Ja osobiscie uzywam referencji, a to dlatego ze o niej najpierw sie uczylem, a pozniej trudno mi sie bylo przyzwyczaic do wskaznikow w tej roli :P
“. Na przykład tablica dostępna przez wskaźnik musiałaby być indeksowana przez (*pArray)[i]” Albo ja nie rozumiem, albo coś przekręciłeś. Jeśli odbieramy tablicę jako argument w funkcji poprze wskaźnik na nią (dekl. funkcji –
int f1(int* wsk) ) , to wewnątrz funkcji do każdego z elemntów odwołujemy się w sposób wsk[i]. Zapis taki jak ty mówisz ( (*ptr)[i]) , tyczy się wskaźnika na wskaźnik na tablicę. Możliwe, że chodziło Ci o to od początku, ale kto się bawi w takie rzeczy, jeśli nie potrzebuje ??
Według mnie mogło też chodzić o klasę z przeciążonym operatorem [], ale nie jestem pewien ;)
Skrót myślowy :) Dla mnie tablica to kolekcja, czyli np. std::vector. W przypadku zwykłych tablic w stylu C jest rzeczywiście tak, jak mówisz.
Poprawiłem notkę odpowiednio, żeby nie robić nieporozumień.
Współcześnie stosuje się chyba określenie parametr wejściowy (in), wyjściowy (out), wejściowy-wyjściowy (inout). Tak robi MSDN, tak robi dokumentacja Doxygen i inne dokumentacje. Tak też powinien robić nowoczesny język programowania i C# ma coś takiego. C++ oczywiście nie ma i dlatego ja sobie oznaczam takie parametry odpowiednio w ich nazwie, np. Funkcja(int *OutValue).
Jak wspomniał Reg, do oznaczania tego, czy parametr jest wejściowy, wyjściowy czy “IO” jest dokumentacja i komentarze. Sam stosuję niewielkie rozwinięcie XMLowego formatu komentarzy z Visual Studio – m.in. zawierające parametr określający czy jest to wskaźnik wejścia czy wyjścia, pole nazwy klasy (bo C# ma np. wkurzającą cechę zamieniania Int32 na int, czy UInt16 na ushort itd.), informację o ewentualnej zmianie wartości wskazywanej (dla referencji do wskaźników w C++ – prtmode=maychange lub ptrmode=alwayschange).
Sam podział referencja/wskaźnik stosuję niemal dokładnie tak, jak Xion. Przez wskaźniki przekazuję parametry opcjonalne (chyba że to parametry typu int, float itp. – wtedy wartość nieprawidłowa dla danego wywołania oznacza brak użycia parametru – przykład: w IMesh::DrawInstances(UINT instCount) jeśli instCount wynosi 0, mesh jest renderowany bez uwzględniania instancingu) oraz wszystkie wskaźniki do zewnętrznych interfejsów (typu ID3D10Device, ID3D10Effect itd.). Cała reszta przez wartość, stałą referencję (dla wszystkiego ponad 64 bity) lub referencję (jeśli modyfikowalne w funkcji).