2009-03-18 16 views
16

Po przeczytaniu kilku tutoriali doszedłem do wniosku, że zawsze należy używać wskaźników do obiektów. Ale widziałem także kilka wyjątków podczas czytania niektórych tutoriali QT (http://zetcode.com/gui/qt4/painting/), gdzie obiekt QPaint jest tworzony na stosie. Teraz jestem zdezorientowany. Kiedy powinienem używać wskaźników?C++: kiedy używać wskaźników?

+0

Samouczki QT tworzą obiekt QPaint na stosie, ponieważ okres przydatności obiektu QPaint jest znany w czasie kompilacji i jest ściśle ograniczony przez dopasowaną parę {}. –

+0

To samo dotyczy QPen, QPainter i QApplication. –

+0

Co jest wspaniale wiedzieć na temat wskaźnika, to że mamy teraz w C++ pewną bezpieczną implementację wskaźników takich jak unique_ptr lub weak_ptr, kiedy trzeba użyć wskaźnika, trzeba ich użyć (z rzadkim wyjątkiem na kodzie niskiego poziomu) – MokaT

Odpowiedz

41

Jeśli nie wiesz, kiedy powinieneś używać wskaźników, po prostu ich nie używaj.

Stanie się oczywiste, gdy trzeba z nich korzystać, każda sytuacja jest inna. Niełatwo jest podsumować zwięźle, kiedy powinny być używane. Nie wdawać się w nawyk "używania wskaźników do przedmiotów", to z pewnością zła rada.

+6

To nie jest odpowiedź. Celem tej witryny jest pomoc ludziom w zdobywaniu większej wiedzy, a nie odrzucaniu ich, gdy nie potrafisz wyrazić swoich myśli. –

+0

@ A.R. Mam nadzieję, że pozwoliłem jamolkhon nauczyć się nie zawsze używać wskaźników - spójrz na pierwsze zdanie na jego pytanie. Wziąłem pod uwagę ten kontekst, a nie tylko tytuł. Poza tym inne pytania zawierają listę niektórych zastosowań. – CiscoIPPhone

+0

Wziąłem pod uwagę ostatnie zdanie, no wiesz, część pytania. –

4

Jakie to samouczki? Właściwie zasada jest taka, że ​​powinieneś używać wskaźników tylko wtedy, gdy absolutnie musisz, co jest dość rzadkie. Musisz przeczytać dobrą książkę o C++, np. Accelerated C++ autorstwa Koeniga & Moo.

Edit: Aby wyjaśnić trochę - dwa przypadki, gdzie byś nie użyć wskaźnika (ciąg jest używany jest tutaj jako egzemplarzu - sam pójdzie do innego rodzaju):

class Person { 
    public: 
     string name;  // NOT string * name; 
    ... 
}; 


void f() { 
    string value;  // NOT string * value 
    // use vvalue 
} 
+0

Co powiecie na pytanie? ograniczenia stosu? – Jamol

+0

jakie byłoby to ograniczenie? –

+0

cóż mam na myśli w niektórych systemach, jeśli nie wszystkie rozmiary stosu są ograniczone, prawda? – Jamol

21

Główne powody używania wskaźników:

  1. czas życia obiektu kontrolnego;
  2. nie może używać referencji (np. Chcesz przechowywać coś, czego nie można skopiować w wektorze);
  3. należy przekazać wskaźnik do funkcji innej firmy;
  4. może niektóre przyczyny optymalizacji, ale nie jestem pewien.
+4

Myślę, że najważniejszym punktem wskaźników jest kontrolowanie czasu życia obiektów. –

+0

Punkt # 2 dotyczy w szczególności Qt, ponieważ większość obiektów związanych z grafiką Qt nie podlega kopiowaniu. –

+0

@caparcode, Właściwie QObjects nie są kopiowalne. Obejmuje to elementy GUI i bardzo niewiele klas graficznych oraz kilka innych rzeczy. Inne klasy również nie mogą być kopiowane z jakiegokolwiek powodu. – strager

1

zazwyczaj używają wskaźniki/odniesień do obiektów, gdy:

  • przekazaniem ich do innych metod

  • tworzących dużą tablicę (nie jestem pewien, co normalny rozmiar stosu jest)

Użyj stos, gdy:

  • Tworzysz obiekt, który żyje i umiera w metodzie

  • Obiekt jest wielkość rejestru procesora lub mniejsze

+0

Prawie wszystko, co tu mówisz, jest nieprawidłowe. –

+0

Tak, jesteś w błędzie. –

+0

Więc obaj: przekazywanie obiektów na stosie, tworzenie dużych tablic na stosie, alokowanie zmiennych lokalnych na stercie, przydzielanie liczb całkowitych na stercie? – LegendLength

0

użyć wskaźników, jeśli nie chcesz, aby obiekt do być zniszczone po opróżnieniu ramy stosu.

Używaj odnośników do przekazywania parametrów tam, gdzie to możliwe.

+0

int x; int * pointer_to_x = & x; Powtarzasz błąd PO między wskaźnikami i alokacją sterty. –

+0

Nie, przekazywanie przez odniesienie do const zamiast wartości jest dobrym nawykiem. Mimo że wykorzystuje wskaźniki na poziomie implementacji, nie ma z nimi nic koncepcyjnego. –

4

Zazwyczaj trzeba użyć wskaźników w następujących scenariuszach:

  1. Trzeba zbiór obiektów, które należą do różnych klas (w większości przypadków będą one mają wspólną bazę).

  2. Potrzebna jest kolekcja obiektów o przydzielonych stertach, tak duża, że ​​prawdopodobnie spowoduje to przepełnienie stosu.

  3. Potrzebna jest struktura danych, która może szybko zmienić kolejność obiektów - podobnie jak lista połączona, drzewo jest podobne.

  4. Potrzebujesz skomplikowanej logiki zarządzania całym swoim życiem.

  5. Potrzebujesz struktury danych, która umożliwia bezpośrednią nawigację z obiektu do obiektu - jak połączona lista, drzewo lub jakikolwiek inny wykres.

+0

W przypadku (1) nie ma "być może" o potrzebie wspólnej podstawy. W przypadku (2) jedyną "kolekcją", która miałaby zastosowanie, byłaby tablica typu C. –

+0

Dla przypadku (1) nie jest konieczne, możesz chcieć przechowywać tablicę pustki *, aby móc po prostu wywołać free() - to nie jest sposób działania C++, ale może być. Dla przypadku (2) miałem na myśli tablicę wskaźników a tablicę rzeczywistych obiektów. Jeśli obiekty są naprawdę dużymi wskaźnikami przechowującymi, oszczędzają dużo miejsca na stosie. – sharptooth

2

Oprócz punktów, które inni wykonują (szczególnie w.r.t. kontrolując czas życia obiektu), jeśli potrzebujesz obsługiwać obiekty NULL, powinieneś używać wskaźników, a nie referencji. Możliwe jest tworzenie odwołania NULL poprzez typowanie, ale generalnie jest to zły pomysł.

+0

To naprawdę okropny pomysł. –

1

I rzeczywiście użyć wskaźników w tej sytuacji:

class Foo 
{ 
    Bar* bar; 

    Foo(Bar& bar) : bar(&bar) { } 

    Bar& Bar() const { return *bar; } 
}; 

Wcześniej użyłem członków referencyjnych, inicjowane z konstruktora, ale kompilator ma problem z utworzeniem konstruktorów kopiowania, operatory przypisania, a dużo.

Dave

15

To nie jest dla mnie jasne, czy pytanie jest ptr do obj vs na stosie-obj lub PTR-do-obj vs obj odniesienie do działania. Istnieją również zastosowania, które nie należą do żadnej z tych kategorii.

Odnośnie vs stosu, który wydaje się już uwzględniony powyżej. Kilka powodów, najbardziej oczywiste, to czas życia obiektu.

chodzi vs odniesienia, zawsze staramy się używać odwołań, ale są rzeczy, które można zrobić tylko z ptrs, na przykład (istnieje wiele zastosowań):

  • chodząc elementów w tablicy (np marsz poprzez standardową tablicę [])
  • gdy zwana funkcja alokuje co & zwraca go, za pośrednictwem PTR

najważniejsze, wskaźniki (i dane, a nie automatyczną/na stosie & statyczne obiekty) wspierają polimorfizm. Wskaźnik do klasy bazowej może faktycznie wskazywać na klasę pochodną. Jest to podstawą zachowania OO wspieranego w C++.

+3

+1 dla ostatniego, pogrubionego, akapitu, ponieważ nie mogę go bardziej upomnieć. –

+0

Ale można przydzielić coś na stosie, a następnie później użyć polimorficznie, przekazując referencję lub wskaźnik do funkcji/kontenera/cokolwiek innego. ** Czas przechowywania i polimorfizm są pojęciami ortogonalnymi. ** Mimo to ludzie nadal odpowiadają na pytania o to, dlaczego można przypisać za pomocą wskaźnika, cytując polimorfizm jako zaletę, nie wyjaśniając jednocześnie, dlaczego te dwie są ze sobą powiązane (nie są). –

10

Po pierwsze, pytanie jest błędne: dylemat nie występuje pomiędzy wskaźnikami a stosem, ale między stosem a stosem. Możesz mieć obiekt na stosie i przekazać wskaźnik do tego obiektu. Zakładam, że naprawdę pytasz, czy powinieneś zadeklarować wskaźnik do klasy lub instancji klasy.

Odpowiedź jest taka, że ​​zależy to od tego, co chcesz zrobić z obiektem. Jeśli obiekt musi istnieć po opuszczeniu funkcji przez kontrolkę, należy użyć wskaźnika i utworzyć obiekt na stercie. Będziesz to robić, na przykład, gdy twoja funkcja musi zwrócić wskaźnik do utworzonego obiektu lub dodać obiekt do listy, która została utworzona przed wywołaniem twojej funkcji.

Z drugiej strony, jeśli obiekty są lokalne dla funkcji, lepiej jest użyć jej na stosie. Umożliwia to kompilatorowi wywołanie destruktora, gdy formant opuszcza funkcję.

1

wykorzystujące wskaźniki związane jest z dwóch prostopadłych rzeczy:

  1. Dynamiczna alokacja. Zasadniczo należy przydzielać dynamicznie, gdy obiekt ma dłużej żyć w zakresie, w którym został utworzony. Taki obiekt jest zasobem, którego właściciel musi być jasno określony (najczęściej jakiś inteligentny wskaźnik).

  2. Uzyskiwanie dostępu przez adres (niezależnie od sposobu utworzenia obiektu). W tym kontekście wskaźnik nie oznacza własności. Taki dostęp może być potrzebny, gdy:

    • wymaga to już istniejący interfejs.
    • stowarzyszenie, które może mieć wartość NULL, powinno być modelowane.
    • należy unikać kopiowania dużych obiektów lub kopiowanie jest niemożliwe, ale odwołanie nie może być używane (np. Zbiory STL).

# 1 i # 2 może występować w różnych konfiguracjach, na przykład można sobie wyobrazić dynamicznie przydzielonego obiektu dostępnego przez wskaźnik, ale taki obiekt mógłby również przekazywane przez odniesienie do niektórych funkcji. Możesz również uzyskać wskaźnik do obiektu, który jest tworzony na stosie, itp.

+0

"Dwie rzeczy ortogonalne" - w końcu ktoś to powiedział! [cf] (http://stackoverflow.com/questions/658133/c-when-to-usepointpoint#comment63979527_658308) Dodałbym do punktu ** 2 **, że dostęp przez odniesienie/wskaźnik (= adres) umożliwia polimorfizm , niezależnie od tego, jak obiekt został przydzielony - w przeciwieństwie do niezrozumiałej liczby stanowisk, widzę, że uzasadnionym powodem alokacji dynamicznej jest przyznanie zachowania polimorficznego - co nie jest wymagane, nie jest argumentem i zupełnie nie ma związku. –

1

Przepuszczanie przez wartość z dobrze zachowanymi obiektami do kopiowania jest sposobem na uzyskanie dużej ilości kodu.

Jeśli prędkość naprawdę ma znaczenie, użyj przejścia przez referencję tam, gdzie możesz i wreszcie użyj wskaźników.

0

Mówiąc o C++, obiekty utworzone na stosie nie mogą być używane, gdy program opuścił zakres, w którym został utworzony. Zasadniczo, gdy wiesz, że nie potrzebujesz zmiennej za funkcją lub za blisko nawiasu klamrowego, możesz go utworzyć na stosie.

Mówiąc konkretnie o Qt, Qt pomaga programistom poprzez obsługę dużej ilości zarządzania pamięcią obiektów sterty. W przypadku obiektów pochodnych od QObject (prawie wszystkie klasy z prefiksem "Q" są), konstruktorzy przyjmują opcjonalny parametr parent. Obiekt nadrzędny jest właścicielem obiektu, a po usunięciu elementu nadrzędnego wszystkie obiekty będące własnością również są usuwane. W gruncie rzeczy odpowiedzialność za zniszczenie dzieci jest przekazywana do obiektu macierzystego. Podczas korzystania z tego mechanizmu, dziecko musi być utworzone na stercie.

W skrócie, w Qt można łatwo tworzyć obiekty na stercie, a tak długo, jak ustawia się właściwego rodzica, trzeba będzie tylko martwić się niszczeniem rodzica. Ogólnie C++, musisz jednak pamiętać, aby niszczyć obiekty sterty lub używać inteligentnych wskaźników.

1

Jeśli to możliwe, nigdy nie używaj wskaźników. Możesz polegać na przekazywaniu przez referencję lub jeśli chcesz zwrócić strukturę lub klasę, załóżmy, że Twój kompilator ma optymalizację wartości zwracanej. (Musisz jednak unikać warunkowej konstrukcji zwróconej klasy).

Jest powód, dla którego Java nie ma wskaźników. C++ też ich nie potrzebuje.Jeśli unikniesz ich użycia, uzyskasz dodatkową korzyść polegającą na automatycznym niszczeniu obiektów, gdy obiekt opuści zakres. W przeciwnym razie twój kod będzie generował błędy pamięci różnych typów. Wycieki pamięci mogą być bardzo trudne do znalezienia i często występują w C++ z powodu nieobsługiwanych wyjątków.

Jeśli musisz używać wskaźników, rozważ niektóre z klas inteligentnych wskaźników, takich jak auto_ptr. Automatyczne niszczenie obiektów to coś więcej niż tylko zwolnienie pamięci bazowej. Istnieje pojęcie o nazwie RAII. Niektóre obiekty wymagają dodatkowo niszczenia. na przykład muteksy i zamykanie plików itp.

+1

Cóż, Java nie "ma" wskaźników, ponieważ wszystko inne niż prymityw jest domyślnie wskaźnikiem. – mk12