Jakie są zalety przejścia przez wskaźnik nad przejściem przez referencję w C ++?
Ostatnio widziałem wiele przykładów, które wybrały przekazywanie argumentów funkcji przez wskaźniki zamiast przekazywania przez odwołanie. Czy są z tego korzyści?
Przykład:
func(SPRITE *x);
z wezwaniem
func(&mySprite);
vs.
func(SPRITE &x);
z wezwaniem
func(mySprite);
c++
pointers
parameter-passing
pass-by-reference
Matt Pascoe
źródło
źródło
new
utworzeniu wskaźnika i wynikających z tego kwestiach własności.Odpowiedzi:
Wskaźnik może otrzymać parametr NULL, parametr referencyjny nie. Jeśli kiedykolwiek istnieje szansa, że możesz przekazać „brak obiektu”, użyj wskaźnika zamiast odwołania.
Przekazywanie przez wskaźnik pozwala również wyraźnie zobaczyć w witrynie wywołującej, czy obiekt jest przekazywany przez wartość, czy przez odniesienie:
źródło
func2 passes by reference
. Chociaż doceniam, że miałeś na myśli, że przekazuje „referencję” z perspektywy wysokiego poziomu, zaimplementowanej przez przekazanie wskaźnika w perspektywie na poziomie kodu, było to bardzo mylące (patrz stackoverflow.com/questions/13382356/... ).func(int& a)
nie jest poprawny C w żadnej wersji standardu. Prawdopodobnie kompilujesz pliki jako C ++ przez przypadek.Przechodząc obok wskaźnika
nothing
. Można to wykorzystać do podania opcjonalnych argumentów.Przekaż przez odniesienie
string s = &str1 + &str2;
używać wskaźników.void f(const T& t); ... f(T(a, b, c));
wskaźniki nie mogą być używane w ten sposób, ponieważ nie można wziąć adresu tymczasowego.źródło
Podoba mi się rozumowanie w artykule z „cplusplus.com:”
Biorę z tego, że główna różnica między wyborem wskaźnika lub parametru referencyjnego polega na tym, czy NULL jest dopuszczalną wartością. Otóż to.
To, czy wartość jest wejściowa, wyjściowa, modyfikowalna itp., Powinno jednak znajdować się w dokumentacji / komentarzach dotyczących funkcji.
źródło
Allen Holub w „Wystarczającej ilości liny, by strzelić sobie w stopę” wymienia następujące 2 zasady:
Wymienia kilka powodów, dla których dodano odniesienia do C ++:
const
referencje pozwalają na semantykę przekazywania wartości przy jednoczesnym unikaniu kopiowaniaJego głównym punktem jest to, że odniesienia nie powinny być używane jako parametry „wyjściowe”, ponieważ w witrynie wywoławczej nie ma wskazania, czy parametr jest parametrem odniesienia, czy wartością. Zatem jego regułą jest używanie
const
referencji wyłącznie jako argumentów.Osobiście uważam, że jest to dobra zasada, ponieważ wyjaśnia, kiedy parametr jest parametrem wyjściowym, czy nie. Jednakże, chociaż osobiście się z tym ogólnie zgadzam, pozwalam się zachwiać opiniami innych członków mojego zespołu, jeśli argumentują za parametrami wyjściowymi jako odniesieniami (niektórzy programiści bardzo je lubią).
źródło
Wyjaśnienia do poprzednich postów:
Referencje NIE są gwarancją otrzymania wskaźnika o wartości innej niż zero. (Chociaż często traktujemy je jako takie.)
Chociaż jest to przerażająco zły kod, ponieważ zabiera cię za zaszyfrowany zły kod, następujące kompilują się i uruchamiają: (Przynajmniej pod moim kompilatorem).
Prawdziwy problem, jaki mam z referencjami, dotyczy innych programistów, odtąd określanych jako IDIOTS , którzy przydzielają w konstruktorze, zwalniają w destruktorze i nie dostarczają konstruktora kopii lub operatora = ().
Nagle istnieje różnica między foo (BAR BAR) a foo (BAR & bar) . (Automatyczna bitowa operacja kopiowania zostaje wywołana. Dezalokacja w destruktorze zostaje wywołana dwukrotnie.)
Na szczęście nowoczesne kompilatory wykryją podwójną dezalokację tego samego wskaźnika. 15 lat temu tego nie zrobili. (Pod gcc / g ++, użyj setenv MALLOC_CHECK_ 0, aby wrócić do starych sposobów.) W rezultacie , w systemie DEC UNIX, ta sama pamięć jest przydzielana do dwóch różnych obiektów. Dużo zabawy przy debugowaniu ...
Bardziej praktycznie:
źródło
*i
, twój program zachowuje się w sposób nieokreślony. Na przykład kompilator może zobaczyć ten kod i założyć „OK, ten kod ma niezdefiniowane zachowanie we wszystkich ścieżkach kodu, więc cała ta funkcja musi być nieosiągalna”. Wtedy założymy, że wszystkie gałęzie prowadzące do tej funkcji nie są brane. Jest to regularnie przeprowadzana optymalizacja.Większość odpowiedzi tutaj nie rozwiązuje wewnętrznej dwuznaczności posiadania surowego wskaźnika w sygnaturze funkcji, jeśli chodzi o wyrażanie zamiaru. Problemy są następujące:
Program wywołujący nie wie, czy wskaźnik wskazuje pojedynczy obiekt, czy początek „tablicy” obiektów.
Dzwoniący nie wie, czy wskaźnik „posiada” pamięć, na którą wskazuje. IE, czy funkcja powinna zwolnić pamięć. (
foo(new int)
- Czy to wyciek pamięci?).Dzwoniący nie wie, czy
nullptr
można bezpiecznie przejść do funkcji.Wszystkie te problemy są rozwiązywane przez referencje:
Odnośniki zawsze odnoszą się do jednego obiektu.
Referencje nigdy nie posiadają pamięci, do której się odnoszą, są jedynie widokiem na pamięć.
Referencje nie mogą mieć wartości zerowej.
To sprawia, że referencje są znacznie lepszym kandydatem do ogólnego użytku. Jednak referencje nie są idealne - należy rozważyć kilka głównych problemów.
&
operatora, aby pokazać, że rzeczywiście mijamy wskaźnik. Na przykładint a = 5; foo(a);
tutaj wcale nie jest jasne, że a jest przekazywane przez odniesienie i może zostać zmodyfikowane.std::optional<T&>
nieważne (z dobrych powodów), wskaźniki dają nam taką pożądalność.Wydaje się więc, że kiedy chcemy nullownego odniesienia z wyraźną pośrednią interwencją, powinniśmy sięgnąć po
T*
prawo? Źle!Abstrakcje
W naszej desperacji na punkcie zerowania możemy sięgnąć
T*
i po prostu zignorować wszystkie niedociągnięcia i semantyczne dwuznaczności wymienione wcześniej. Zamiast tego powinniśmy sięgnąć po to, co C ++ robi najlepiej: abstrakcję. Jeśli po prostu napiszemy klasę, która owija się wokół wskaźnika, zyskujemy wyrazistość, a także nullability i wyraźną pośredniość.Jest to najprostszy interfejs, jaki mogłem wymyślić, ale działa on skutecznie. Pozwala na zainicjowanie odwołania, sprawdzenie, czy wartość istnieje i dostęp do wartości. Możemy go używać w następujący sposób:
Osiągnęliśmy nasze cele! Zobaczmy teraz zalety w porównaniu do surowego wskaźnika.
nullptr
można go przekazać, ponieważ autor funkcji wyraźnie prosi o polecenieoptional_ref
Możemy sprawić, że interfejs stanie się bardziej złożony, na przykład dodając operatory równości, interfejs monadyczny
get_or
imap
interfejs, metodę, która pobiera wartość lub zgłasza wyjątek,constexpr
obsługę. Możesz to zrobić przez ciebie.Podsumowując, zamiast używać surowych wskaźników, uzasadnij, co te wskaźniki faktycznie oznaczają w kodzie, albo wykorzystaj standardową abstrakcję biblioteki lub napisz własną. Znacząco poprawi to Twój kod.
źródło
Nie całkiem. Wewnętrznie przekazywanie przez odniesienie odbywa się poprzez zasadniczo przekazanie adresu odnośnego obiektu. Tak więc, naprawdę nie ma żadnego przyrostu wydajności po przejściu wskaźnika.
Przekazywanie przez odniesienie ma jednak jedną zaletę. Na pewno masz instancję dowolnego obiektu / typu, który jest przekazywany. Jeśli podasz wskaźnik, ryzykujesz otrzymaniem wskaźnika NULL. Korzystając z funkcji przekazywania według referencji, wypychasz niejawne sprawdzenie NULL o jeden poziom w górę do programu wywołującego twoją funkcję.
źródło