Rozumiem składnię i ogólną semantykę wskaźników w porównaniu z referencjami, ale jak mam zdecydować, kiedy bardziej lub mniej właściwe jest używanie referencji lub wskaźników w interfejsie API?
Oczywiście niektóre sytuacje wymagają jednej lub drugiej ( operator++
wymaga argumentu referencyjnego), ale ogólnie uważam, że wolę używać wskaźników (i wskaźników stałych), ponieważ składnia jest jasna, że zmienne są przekazywane destrukcyjnie.
Np. W następującym kodzie:
void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
}
Dzięki wskaźnikowi zawsze (bardziej) oczywiste jest, co się dzieje, więc w przypadku interfejsów API i tym podobnych, w których istotną kwestią jest przejrzystość, wskaźniki nie są bardziej odpowiednie niż odniesienia? Czy to oznacza, że odniesienia należy stosować tylko wtedy, gdy jest to konieczne (np. operator++
)? Czy są jakieś problemy z wydajnością jednego lub drugiego?
EDYCJA (NIEAKTUALIZOWANE):
Poza dopuszczeniem wartości NULL i radzeniem sobie z surowymi tablicami wydaje się, że wybór sprowadza się do osobistych preferencji. Zaakceptowałem odpowiedź poniżej, która odwołuje się do Przewodnika po stylu Google C ++ , ponieważ prezentują pogląd, że „Odnośniki mogą być mylące, ponieważ mają składnię wartości, ale semantykę wskaźnika”.
Z powodu dodatkowej pracy wymaganej do dezynfekcji argumentów wskaźnika, które nie powinny mieć wartości NULL (np add_one(0)
. Wywoła wersję wskaźnika i zepsuje się w czasie wykonywania), z punktu widzenia łatwości konserwacji sensowne jest używanie referencji tam, gdzie MUSI być obecny obiekt, choć szkoda stracić przejrzystość składniową.
add_one(a);
jest niejasnego, żea
zostanie zmodyfikowany? W kodzie jest napisane: dodaj jeden .addOneTo(...)
. Jeśli nie to chcesz zrobić, spójrz na deklarację.Odpowiedzi:
Używaj referencji, gdziekolwiek możesz, wskaźników, gdziekolwiek musisz.
Unikaj wskaźników, dopóki nie będziesz w stanie.
Powodem jest to, że wskaźniki utrudniają śledzenie / czytanie, mniej bezpieczne i znacznie bardziej niebezpieczne manipulacje niż jakiekolwiek inne konstrukcje.
Zatem podstawową zasadą jest używanie wskaźników tylko wtedy, gdy nie ma innego wyboru.
Na przykład, zwracanie wskaźnika do obiektu jest prawidłową opcją, gdy funkcja może w niektórych przypadkach zwrócić nullptr i zakłada się, że tak. To powiedziawszy, lepszym rozwiązaniem byłoby użycie czegoś podobnego do
boost::optional
.Innym przykładem jest użycie wskaźników do surowej pamięci dla określonych manipulacji pamięcią. Powinno to być ukryte i zlokalizowane w bardzo wąskich częściach kodu, aby pomóc ograniczyć niebezpieczne części całej bazy kodu.
W twoim przykładzie nie ma sensu używać wskaźnika jako argumentu, ponieważ:
nullptr
jako argument, przejdziesz do krainy niezdefiniowanych zachowań;Jeśli zachowanie funkcji musiałoby działać z danym obiektem lub bez niego, wówczas użycie wskaźnika jako atrybutu sugeruje, że można podać
nullptr
jako argument i jest to w porządku dla funkcji. To rodzaj umowy między użytkownikiem a wdrożeniem.źródło
add_one(a)
nie powinien zwrócić wyniku, zamiast ustawić go przez odniesienie?add_one(a)
jest mylący, to dlatego, że ma niepoprawną nazwę.add_one(&a)
miałby to samo zamieszanie, tylko teraz możesz zwiększać wskaźnik, a nie obiekt.add_one_inplace(a)
uniknęłoby wszelkiego zamieszania.NULL
inullptr
, i ma je z jakiegoś powodu. I nie jest to dobrze przemyślana ani nawet realistyczna rada, aby dać „nigdy nie używaj wskaźników” i / lub „nigdy nie używaj NULL, zawsze używajboost::optional
”. To po prostu szalone. Nie zrozum mnie źle, surowe wskaźniki są potrzebne w C ++ rzadziej niż w C, ale nadal są przydatne, nie są tak „niebezpieczne”, jak niektórzy ludzie w C ++ lubią twierdzić (to też przesada), i znowu: kiedy łatwiej jest po prostu użyć wskaźnika ireturn nullptr;
wskazać brakującą wartość ... Po co importować cały Boost?if
jest przestarzały i należy go użyćwhile() { break; }
, prawda? Ponadto, nie martw się, widziałem i pracowałem z dużymi bazami kodu, i tak, jeśli jesteś nieostrożny , wówczas własność jest problemem. Nie, jeśli będziesz trzymać się konwencji, używaj ich konsekwentnie oraz komentuj i dokumentuj swój kod. Ale w końcu powinienem po prostu użyć C, bo jestem zbyt głupi, by używać C ++, prawda?Wydajności są dokładnie takie same, ponieważ odniesienia są implementowane wewnętrznie jako wskaźniki. Dlatego nie musisz się o to martwić.
Nie ma ogólnie przyjętej konwencji dotyczącej tego, kiedy używać odniesień i wskaźników. W kilku przypadkach musisz zwrócić lub zaakceptować referencje (na przykład kopiować konstruktora), ale poza tym możesz robić to, co chcesz. Dość powszechną konwencją, z jaką się spotkałem, jest używanie odwołań, gdy parametr musi odwoływać się do istniejącego obiektu, oraz wskaźników, gdy wartość NULL jest prawidłowa.
Niektóre konwencje kodowania (takie jak Google ) nakazują, aby zawsze używać wskaźników lub stałych referencji, ponieważ referencje mają trochę niejasną składnię: mają zachowanie referencji, ale składnię wartości.
źródło
add_one
jest uszkodzony :add_one(0); // passing a perfectly valid pointer value
, Kaboom. Musisz sprawdzić, czy jest pusty. Niektóre osoby będą odpowiadać: „no cóż, po prostu udokumentuję, że moja funkcja nie działa z null”. W porządku, ale potem pokonujesz cel pytania: jeśli przejrzysz dokumentację, aby sprawdzić, czy wartość null jest w porządku, zobaczysz także deklarację funkcji .int& i = *((int*)0);
To nie jest poprawna retorta. Problem w poprzednim kodzie dotyczy użycia wskaźnika, a nie referencji . Referencje nigdy nie są zerowe, kropka.Z C ++ FAQ Lite -
źródło
C with class
(co nie jest C ++).Moja ogólna zasada brzmi:
&
)const
to parametr przychodzący)const T&
).int ¤t = someArray[i]
)Niezależnie od tego, którego używasz, nie zapomnij udokumentować swoich funkcji i znaczenia ich parametrów, jeśli nie są one oczywiste.
źródło
Oświadczenie: poza tym, że referencje nie mogą mieć wartości NULL ani „odbicia” (co oznacza, że nie mogą zmienić obiektu, którego są pseudonimem), tak naprawdę sprowadza się to do gustu, więc nie powiem "to jest lepsze".
To powiedziawszy, nie zgadzam się z twoim ostatnim stwierdzeniem w poście, ponieważ nie sądzę, aby kod stracił jasność w odniesieniu do odniesień. W twoim przykładzie
może być jaśniejsze niż
ponieważ wiesz, że najprawdopodobniej wartość a się zmieni. Z drugiej jednak strony podpis funkcji
jest nieco niejasne: czy n będzie pojedynczą liczbą całkowitą czy tablicą? Czasami masz dostęp tylko do (słabo udokumentowanych) nagłówków i podobnych podpisów
nie są łatwe do interpretacji na pierwszy rzut oka.
Imho, referencje są tak dobre jak wskaźniki, gdy nie jest potrzebny (ponowny) przydział ani ponowne wiązanie (w sensie wyjaśnionym wcześniej). Ponadto, jeśli programista używa tylko wskaźników do tablic, sygnatury funkcji są nieco mniej dwuznaczne. Nie wspominając już o tym, że składnia operatorów jest znacznie bardziej czytelna dzięki referencjom.
źródło
Podobnie jak inni już odpowiedzieli: Zawsze używaj referencji, chyba że zmienna będąca
NULL
/nullptr
jest naprawdę poprawnym stanem.Pogląd Johna Carmacka na ten temat jest podobny:
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
Edytuj 2012-03-13
Użytkownik Bret Kuhns słusznie zauważa:
To prawda, ale pytanie wciąż pozostaje, nawet przy zastępowaniu surowych wskaźników inteligentnymi wskaźnikami.
Na przykład, zarówno
std::unique_ptr
istd::shared_ptr
może być zbudowany jako „pustych” wskaźników poprzez ich domyślnego konstruktora:... co oznacza, że używanie ich bez sprawdzenia, czy nie są puste, grozi awarią, o to właśnie chodzi w dyskusji J. Carmacka.
A potem mamy zabawny problem: „jak przekazać inteligentny wskaźnik jako parametr funkcji?”
Jon „s odpowiedź na pytanie C ++ - przechodzącą odniesień do boost :: shared_ptr oraz następujące komentarze pokazują, że nawet wtedy, przechodząc inteligentnego wskaźnika przez kopii lub poprzez odniesienie nie jest tak jednoznaczne, jak chciałoby (I sprzyjać pragnienia" przez odniesienie ”domyślnie, ale mogę się mylić).
źródło
shared_ptr
iunique_ptr
. Semantyka własności i konwencje parametrów wejścia / wyjścia są obsługiwane przez połączenie tych trzech elementów i stałości. W C ++ prawie nie ma potrzeby używania surowych wskaźników, z wyjątkiem przypadków, gdy mamy do czynienia ze starszym kodem i bardzo zoptymalizowanymi algorytmami. Te obszary, w których są używane, powinny być jak najbardziej zamknięte i przekształcać nieprzetworzone wskaźniki w semantycznie odpowiedni „nowoczesny” odpowiednik.To nie jest kwestia gustu. Oto kilka ostatecznych zasad.
Jeśli chcesz odwoływać się do statycznie zadeklarowanej zmiennej w zakresie, w którym została zadeklarowana, użyj odwołania w C ++, a będzie to całkowicie bezpieczne. To samo dotyczy statycznie deklarowanego inteligentnego wskaźnika. Przekazywanie parametrów przez referencję jest przykładem tego użycia.
Jeśli chcesz odwoływać się do czegokolwiek z zakresu, który jest szerszy niż zakres, w którym jest zadeklarowany, powinieneś użyć inteligentnego wskaźnika z licznikiem referencyjnym, aby był całkowicie bezpieczny.
Możesz odwoływać się do elementu kolekcji z odniesieniem dla wygody składniowej, ale nie jest to bezpieczne; element można usunąć w dowolnym momencie.
Aby bezpiecznie przechowywać odniesienie do elementu kolekcji, musisz użyć inteligentnego wskaźnika z licznikiem odniesień.
źródło
Każda różnica w wydajności byłaby tak mała, że nie uzasadniałaby zastosowania mniej przejrzystego podejścia.
Po pierwsze, jednym z przypadków, o których nie wspomniano, w których referencje są na ogół lepsze, są
const
referencje. W przypadku typów nieprostych przekazanieconst reference
znaku unika tworzenia tymczasowego i nie powoduje zamieszania, o które się martwisz (ponieważ wartość nie jest modyfikowana). W tym przypadku zmuszanie osoby do podania wskaźnika powoduje zamieszanie, o które się martwisz, ponieważ widok adresu odebranego i przekazanego do funkcji może sprawić, że pomyślisz, że wartość się zmieniła.W każdym razie zasadniczo się z tobą zgadzam. Nie podoba mi się, że funkcje przyjmują odwołania, aby zmodyfikować ich wartość, gdy nie jest bardzo oczywiste, że właśnie to robi funkcja. Ja też wolę używać wskaźników w tym przypadku.
Kiedy musisz zwrócić wartość w typie złożonym, wolę odwołania. Na przykład:
Tutaj nazwa funkcji wyjaśnia, że otrzymujesz informacje z powrotem do tablicy. Więc nie ma zamieszania.
Główną zaletą referencji jest to, że zawsze zawierają poprawną wartość, są czystsze niż wskaźniki i obsługują polimorfizm bez potrzeby dodatkowej składni. Jeśli żadna z tych zalet nie ma zastosowania, nie ma powodu, aby preferować odniesienie nad wskaźnikiem.
źródło
Skopiowano z wiki -
Zgadzam się w 100% z tym i dlatego uważam, że powinieneś używać referencji tylko wtedy, gdy masz bardzo dobry powód.
źródło
Należy pamiętać o następujących kwestiach:
Wskaźniki mogą być
NULL
, odwołania nie mogą byćNULL
.Referencje są łatwiejsze w użyciu,
const
mogą być używane jako referencje, gdy nie chcemy zmieniać wartości i potrzebujemy tylko referencji w funkcji.Wskaźnik używany z
*
odniesieniami while używany z&
.Użyj wskaźników, gdy wymagana jest arytmetyka wskaźnika.
Możesz mieć wskaźniki do typu pustki,
int a=5; void *p = &a;
ale nie możesz mieć odniesienia do typu pustki.Odniesienie do wskaźnika Vs
Sprawdź, kiedy użyć czego
Wskaźnik : do tablic, list linków, implementacji drzew i arytmetyki wskaźników.
Odniesienie : W parametrach funkcji i typach zwracanych.
źródło
Wystąpił problem z regułą „ używaj referencji tam, gdzie to możliwe ” i pojawia się, jeśli chcesz zachować referencje do dalszego wykorzystania. Aby zilustrować to przykładem, wyobraź sobie, że masz następujące klasy.
Na początku dobrym pomysłem może być
RefPhone(const SimCard & card)
przekazanie parametru do konstruktora przez referencję, ponieważ zapobiega on przekazywaniu niewłaściwych / zerowych wskaźników do konstruktora. W jakiś sposób zachęca do przydzielania zmiennych na stosie i czerpania korzyści z RAII.Ale potem przybywają tymczasowcy, by zniszczyć wasz szczęśliwy świat.
Więc jeśli ślepo trzymasz się referencji, rezygnujesz z możliwości przekazania niepoprawnych wskaźników dla możliwości przechowywania referencji do zniszczonych obiektów, co ma w zasadzie taki sam efekt.
edycja: Pamiętaj, że trzymałem się zasady „Używaj referencji, gdziekolwiek możesz, wskaźników tam, gdzie musisz. Unikaj wskaźników, dopóki nie możesz”. od najbardziej uprzywilejowanej i zaakceptowanej odpowiedzi (inne odpowiedzi również to sugerują). Chociaż powinno to być oczywiste, przykładem nie jest pokazanie, że odniesienia jako takie są złe. Mogą być jednak niewłaściwie wykorzystywane, podobnie jak wskaźniki i mogą wnieść własne zagrożenia do kodu.
Istnieją następujące różnice między wskaźnikami i odniesieniami.
Biorąc to pod uwagę, moje obecne zasady są następujące.
źródło
const SimCard & m_card;
to po prostu źle napisany kod.const SimCard & m_card
jest poprawne, czy nie, zależy od kontekstu. Wiadomość w tym poście nie jest taka, że referencje są niebezpieczne (mogą być, jeśli się bardzo postaramy). Przesłanie jest takie, że nie powinieneś ślepo trzymać się mantry „używaj referencji, kiedy to możliwe”. Przykład jest wynikiem agresywnego użycia doktryny „używaj referencji, gdy tylko jest to możliwe”. To powinno być jasne.const SimCard & m_card
. Jeśli chcesz być efektywny z tymczasowymi, dodajexplicit RefPhone(const SimCard&& card)
konstruktora.Poniżej przedstawiono niektóre wytyczne.
Funkcja wykorzystuje przekazane dane bez ich modyfikacji:
Jeśli obiekt danych jest mały, taki jak wbudowany typ danych lub mała struktura, przekaż go według wartości.
Jeśli obiektem danych jest tablica, użyj wskaźnika, ponieważ jest to twój jedyny wybór. Ustaw wskaźnik jako wskaźnik do stałej.
Jeśli obiekt danych ma dobrą strukturę, użyj wskaźnika const lub odwołania const, aby zwiększyć wydajność programu. Zaoszczędzisz czas i miejsce potrzebne do skopiowania struktury lub projektu klasy. Ustaw wskaźnik lub referencję jako stałą.
Jeśli obiekt danych jest obiektem klasy, użyj stałego odwołania. Semantyka projektowania klas często wymaga użycia odwołania, co jest głównym powodem, dla którego C ++ dodało tę funkcję. Dlatego standardowym sposobem przekazywania argumentów obiektu klasy jest odwołanie.
Funkcja modyfikuje dane w funkcji wywołującej:
1. Jeśli obiekt danych jest wbudowanym typem danych, użyj wskaźnika. Jeśli zauważysz kod taki jak fixit (& x), gdzie x jest liczbą całkowitą, jasne jest, że ta funkcja zamierza zmodyfikować x.
2. Jeśli obiekt danych jest tablicą, użyj jedynego wyboru: wskaźnika.
3. Jeśli obiekt danych jest strukturą, użyj odwołania lub wskaźnika.
4. Jeśli obiekt danych jest obiektem klasy, użyj odwołania.
Oczywiście są to tylko wytyczne i mogą istnieć powody do dokonywania różnych wyborów. Na przykład cin używa referencji dla podstawowych typów, dzięki czemu można używać cin >> n zamiast cin >> & n.
źródło
Referencje są czystsze i łatwiejsze w użyciu, i lepiej ukrywają informacje. Referencje nie mogą być jednak ponownie przypisane. Jeśli musisz najpierw wskazać jeden obiekt, a następnie drugi, musisz użyć wskaźnika. Referencje nie mogą mieć wartości NULL, więc jeśli istnieje jakakolwiek szansa, że przedmiotowy obiekt może mieć wartość NULL, nie wolno używać referencji. Musisz użyć wskaźnika. Jeśli chcesz samodzielnie manipulować obiektami, tzn. Jeśli chcesz przydzielić miejsce w pamięci dla obiektu na stercie zamiast na stosie, musisz użyć wskaźnika
źródło
Twój poprawnie napisany przykład powinien wyglądać
Dlatego referencje są w miarę możliwości preferowane ...
źródło
Po prostu wkładam swoją dziesięciocentówkę. Właśnie wykonałem test. W tym podstępny. Po prostu pozwoliłem g ++ utworzyć pliki asemblera tego samego miniprogramu przy użyciu wskaźników w porównaniu z użyciem referencji. Patrząc na wynik, są dokładnie takie same. Inne niż nazwy symboli. Patrząc na wydajność (w prostym przykładzie) nie ma problemu.
Teraz na temat wskaźników vs. referencji. IMHO Myślę, że jasność stoi ponad wszystkim. Jak tylko przeczytam niejawne zachowanie, moje palce u stóp zaczynają się zwijać. Zgadzam się, że miło jest domniemywać, że odwołanie nie może mieć wartości NULL.
Dereferencje wskaźnika NULL nie stanowią problemu. spowoduje to awarię aplikacji i będzie łatwe do debugowania. Dużym problemem są niezainicjowane wskaźniki zawierające nieprawidłowe wartości. Najprawdopodobniej spowoduje to uszkodzenie pamięci, powodując niezdefiniowane zachowanie bez wyraźnego źródła.
Myślę, że tutaj odniesienia są znacznie bezpieczniejsze niż wskaźniki. I zgadzam się z poprzednim stwierdzeniem, że interfejs (który powinien być jasno udokumentowany, patrz projekt na podstawie umowy, Bertrand Meyer) określa wynik parametrów funkcji. Teraz, biorąc to wszystko pod uwagę, moje preferencje idą do korzystania z referencji gdziekolwiek / kiedykolwiek to możliwe.
źródło
W przypadku wskaźników musisz je wskazać na coś, więc wskaźniki kosztują miejsce w pamięci.
Na przykład funkcja, która pobiera wskaźnik liczb całkowitych, nie przyjmuje zmiennej całkowitej. Musisz więc utworzyć wskaźnik, aby ten pierwszy przekazał funkcję.
Jeśli chodzi o odniesienie, nie będzie to kosztować pamięci. Masz zmienną całkowitą i możesz przekazać ją jako zmienną odniesienia. Otóż to. Nie musisz specjalnie dla niego tworzyć zmiennej referencyjnej.
źródło
&address
. Referencja z pewnością będzie kosztować pamięć, jeśli jest członkiem obiektu, a ponadto wszystkie istniejące kompilatory faktycznie implementują referencje jako adresy, więc nie oszczędzasz ani na przekazywaniu parametrów, ani na dereferencji.