W jakich okolicznościach chciałbyś użyć tego rodzaju kodu w języku c ++?
void foo(type *&in) {...}
void fii() {
type *choochoo;
...
foo(choochoo);
}
W jakich okolicznościach chciałbyś użyć tego rodzaju kodu w języku c ++?
void foo(type *&in) {...}
void fii() {
type *choochoo;
...
foo(choochoo);
}
Odpowiedzi:
Chciałbyś przekazać wskaźnik przez odniesienie, jeśli musisz zmodyfikować wskaźnik, a nie obiekt, na który wskazuje wskaźnik.
Jest to podobne do tego, dlaczego używane są podwójne wskaźniki; używanie odniesienia do wskaźnika jest nieco bezpieczniejsze niż używanie wskaźników.
delete
lub ponownie powiązać ten wskaźnik z innym miejscem w pamięci? A może źle to zrozumiałem?
[]
operatora. Byłby to jeden możliwy (i faktycznie naturalny) podpis const T &operator[](size_t index) const
. Ale możesz też mieć T &operator[](size_t index)
. Możesz mieć jedno i drugie w tym samym czasie. Ta ostatnia pozwoli ci robić takie rzeczy jak myArray[jj] = 42
. Jednocześnie nie podajesz wskaźnika do swoich danych, więc dzwoniący nie może zepsuć pamięci (np. Usunąć ją przypadkowo).
50% programistów C ++ lubi ustawiać swoje wskaźniki na null po usunięciu:
template<typename T>
void moronic_delete(T*& p)
{
delete p;
p = nullptr;
}
Bez odwołania zmieniłbyś tylko lokalną kopię wskaźnika, nie wpływając na wywołującego.
paranoid_delete
, ale Puppy zmienił nazwę na moronic_delete
. Prawdopodobnie należy do pozostałych 50%;) W każdym razie, krótka odpowiedź jest taka, że ustawienie wskaźników na null po delete
prawie nigdy nie jest przydatne (bo i tak powinny wykraczać poza zakres) i często utrudnia wykrycie błędów „użyj po zwolnieniu”.
delete
jest głupi. Szczeniaku, trochę googlowałem, nie rozumiem, dlaczego kasowanie jest kompletnie bezużyteczne (może ja też jestem okropnym googlerem;)). Czy możesz wyjaśnić trochę więcej lub podać link?
Odpowiedź Davida jest poprawna, ale jeśli nadal jest trochę abstrakcyjna, oto dwa przykłady:
Możesz chcieć wyzerować wszystkie zwolnione wskaźniki, aby wcześniej wychwycić problemy z pamięcią. W stylu C zrobiłbyś:
void freeAndZero(void** ptr)
{
free(*ptr);
*ptr = 0;
}
void* ptr = malloc(...);
...
freeAndZero(&ptr);
W C ++, aby zrobić to samo, możesz zrobić:
template<class T> void freeAndZero(T* &ptr)
{
delete ptr;
ptr = 0;
}
int* ptr = new int;
...
freeAndZero(ptr);
W przypadku list połączonych - często przedstawianych po prostu jako wskaźniki do następnego węzła:
struct Node
{
value_t value;
Node* next;
};
W takim przypadku, kiedy wstawiasz do pustej listy, koniecznie musisz zmienić przychodzący wskaźnik, ponieważ wynik nie jest NULL
już wskaźnikiem. Jest to przypadek, w którym modyfikujesz zewnętrzny wskaźnik z funkcji, aby miał odniesienie do wskaźnika w swoim podpisie:
void insert(Node* &list)
{
...
if(!list) list = new Node(...);
...
}
W tym pytaniu jest przykład .
Musiałem użyć kodu takiego jak ten, aby zapewnić funkcje do przydzielania pamięci do przekazanego wskaźnika i zwracania jego rozmiaru, ponieważ moja firma „obiekt” do mnie przy użyciu STL
int iSizeOfArray(int* &piArray) {
piArray = new int[iNumberOfElements];
...
return iNumberOfElements;
}
To nie jest miłe, ale wskaźnik musi być przekazywany przez odniesienie (lub użyj podwójnego wskaźnika). Jeśli nie, pamięć jest przydzielana do lokalnej kopii wskaźnika, jeśli jest przekazywana przez wartość, co powoduje wyciek pamięci.
Jednym z przykładów jest sytuacja, gdy piszesz funkcję parsera i przekazujesz jej wskaźnik źródła do odczytu, jeśli funkcja ma popchnąć ten wskaźnik do przodu za ostatni znak, który został poprawnie rozpoznany przez parser. Użycie odniesienia do wskaźnika wyjaśnia, że funkcja przesunie oryginalny wskaźnik, aby zaktualizować jego położenie.
Ogólnie rzecz biorąc, używasz odwołań do wskaźników, jeśli chcesz przekazać wskaźnik do funkcji i pozwolić mu przenieść oryginalny wskaźnik w inne miejsce, zamiast po prostu przesuwać jego kopię bez wpływania na oryginał.
Inną sytuacją, w której możesz tego potrzebować, jest to, że masz kolekcję wskaźników stl i chcesz je zmienić za pomocą algorytmu stl. Przykład for_each w języku c ++ 98.
struct Storage {
typedef std::list<Object*> ObjectList;
ObjectList objects;
void change() {
typedef void (*ChangeFunctionType)(Object*&);
std::for_each<ObjectList::iterator, ChangeFunctionType>
(objects.begin(), objects.end(), &Storage::changeObject);
}
static void changeObject(Object*& item) {
delete item;
item = 0;
if (someCondition) item = new Object();
}
};
W przeciwnym razie, jeśli używasz podpisu changeObject (obiekt *) , masz kopię wskaźnika, a nie oryginalną.