Dlaczego powinienem używać wskaźnika, a nie samego obiektu?

Pochodzę z tła Javy i zacząłem pracować z obiektami w C++. Ale jedna rzecz, która przyszła mi do głowy, to to, że ludzie często używają wskaźników do obiektów, a nie samych obiektów, na przykład ta deklaracja: {]}

Object *myObject = new Object;

Zamiast:

Object myObject;

Lub zamiast używać funkcji, powiedzmy testFunc(), Jak to:

myObject.testFunc();

Musimy napisać:

myObject->testFunc();
Ale nie mogę zrozumieć, dlaczego mamy to zrobić w ten sposób. Zakładam, że ma to związek z efektywnością i prędkość, ponieważ mamy bezpośredni dostęp do adresu pamięci. Mam rację?
Author: Peter Mortensen, 2014-03-03

22 answers

To bardzo niefortunne, że tak często widzisz dynamiczną alokację. To tylko pokazuje, ilu jest złych programistów C++.

W pewnym sensie masz dwa pytania połączone w jedno. Pierwszy to kiedy powinniśmy użyć alokacji dynamicznej (używając new)? Po drugie, kiedy powinniśmy używać wskaźników?

Ważną wiadomością jest to, że powinieneś zawsze używać odpowiedniego narzędzia do zadania. W prawie każdej sytuacji jest coś bardziej odpowiedniego i bezpieczniejszego niż wykonywanie ręcznej alokacji dynamicznej i / lub używanie surowych wskaźników.

Dynamiczna alokacja

Zademonstrowałeś dwa sposoby tworzenia obiektu. Główną różnicą jest czas przechowywania obiektu. Podczas wykonywania Object myObject; wewnątrz bloku, obiekt jest tworzony z automatycznym czasem przechowywania, co oznacza, że zostanie automatycznie zniszczony, gdy wyjdzie poza zakres. Po wykonaniu new Object() Obiekt ma dynamiczny czas przechowywania, co oznacza, że pozostaje żywy do you explicite delete it. Dynamiczny czas przechowywania danych powinien być używany tylko wtedy, gdy jest potrzebny. Oznacza to, że powinieneś zawsze preferować tworzenie obiektów z automatycznym czasem przechowywania, gdy możesz .

Główne dwie sytuacje, w których możesz wymagać dynamicznej alokacji:

  1. potrzebny jest obiekt, aby przetrwać bieżący zakres - ten konkretny obiekt w określonym miejscu pamięci, a nie jego kopia. Jeśli nie masz nic przeciwko kopiowaniu / przenoszeniu obiektu (przez większość czasu powinieneś być), powinieneś preferować obiekt automatyczny.
  2. musisz przydzielić dużo pamięci , która może łatwo zapełnić stos. Byłoby miło, gdybyśmy nie musieli się tym przejmować (w większości przypadków nie powinno się tego robić), ponieważ jest to naprawdę poza zakresem C++, ale niestety musimy poradzić sobie z rzeczywistością systemów, dla których tworzymy.

Kiedy absolutnie potrzebujesz dynamicznej alokacji, powinieneś zamknÄ ... Ä ‡ go w inteligentnym wskaĺşniku lub innym typie wykonujÄ ... cym RAII (jak standardowe kontenery). Inteligentne Wskaźniki zapewniają semantykę własności dynamicznie przydzielanych obiektów. Zobacz też std::unique_ptr oraz std::shared_ptr, na przykład. Jeśli używasz ich odpowiednio, możesz prawie całkowicie uniknąć samodzielnego zarządzania pamięcią (zobacz Rule of Zero ).

Wskaźniki

Istnieją jednak inne bardziej ogólne zastosowania surowych wskaźników poza dynamiczna alokacja, ale większość ma alternatywy, które powinieneś preferować. Tak jak poprzednio, zawsze preferuj alternatywy, chyba że naprawdę potrzebujesz wskaźników .

  1. Potrzebujesz semantyki referencyjnej . Czasami chcesz przekazać obiekt za pomocą wskaźnika (niezależnie od tego, jak został przydzielony), ponieważ chcesz, aby funkcja, do której go przekazujesz, miała dostęp do tego konkretnego obiektu (a nie jego kopii). Jednak w większości sytuacji należy preferować typy referencyjne do wskaźników, bo właśnie do tego są przeznaczone. Zauważ, że niekoniecznie chodzi o wydłużenie życia obiektu poza bieżący zakres, jak w sytuacji 1 powyżej. Tak jak wcześniej, jeśli nie masz nic przeciwko przekazaniu kopii obiektu, nie potrzebujesz semantyki referencyjnej.

  2. Potrzebujesz polimorfizmu . Funkcje można wywoływać tylko polimorficznie (tzn. w zależności od dynamicznego typu obiektu) za pomocą wskaźnika lub odniesienia do obiektu. Jeśli to jest zachowanie, którego potrzebujesz, a następnie musisz użyć wskaźników lub odniesień. Ponownie, odniesienia powinny być preferowane.

  3. Chcesz reprezentować, że obiekt jest opcjonalny , zezwalając na przekazywanie nullptr, gdy obiekt jest pomijany. Jeśli jest to argument, powinieneś użyć domyślnych argumentów lub przeciążeń funkcji. W przeciwnym razie powinieneś preferować typ, który enkapsuluje to zachowanie, np. std::optional (wprowadzony w C++17 - przy wcześniejszych standardach C++, użyj boost::optional).

  4. Chcesz oddzielić jednostki kompilacji, aby poprawić czas kompilacji . Użyteczna właściwość wskaźnika polega na tym, że wymagane jest tylko przekazanie deklaracji typu wskazywany-do (aby rzeczywiście użyć obiektu, potrzebujesz definicji). Pozwala to na oddzielenie części procesu kompilacji, co może znacznie poprawić czas kompilacji. Zobacz idiom Pimpl .

  5. Musisz połączyć się z biblioteką C lub Biblioteka w stylu C. W tym momencie jesteś zmuszony do używania nieprzetworzonych wskaźników. Najlepszą rzeczą, jaką możesz zrobić, to upewnić się, że tylko pozwól surowym wskaźnikom stracić w ostatnim możliwym momencie. Możesz uzyskać surowy wskaźnik z inteligentnego wskaźnika, na przykład, używając jego funkcji get member. Jeśli Biblioteka wykonuje dla Ciebie przydział, który oczekuje od Ciebie dealokacji za pomocą uchwytu, często możesz zawinąć uchwyt w inteligentny wskaźnik za pomocą niestandardowego deletera, który dealokuje obiekt odpowiednio.

 1371
Author: Joseph Mansfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-13 11:52:08

Istnieje wiele przypadków użycia wskaźników.

Zachowanie polimorficzne . W przypadku typów polimorficznych stosuje się wskaźniki (lub odniesienia), aby uniknąć krojenia:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Semantyka odniesienia i unikanie kopiowania . W przypadku typów nie polimorficznych wskaźnik (lub odniesienie) uniknie kopiowania potencjalnie drogiego obiektu

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

Zauważ, że C++11 ma semantykę move, która pozwala uniknąć wielu kopii drogich obiektów na argument funkcji i jako wartości zwracane. Ale za pomocą wskaźnik z pewnością ich uniknie i pozwoli na wiele wskaźników na ten sam obiekt(podczas gdy obiekt może być przeniesiony tylko raz).

Pozyskiwanie zasobów . Tworzenie wskaźnika do zasobu za pomocą operatora new jest anty-wzorcem w nowoczesnym C++. Użyj specjalnej klasy zasobów (jednego ze standardowych kontenerów) lub inteligentnego wskaźnika (std::unique_ptr<> lub std::shared_ptr<>). Consider:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

Vs.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Surowy wskaźnik powinien być używany tylko jako " widok" i nie w żaden sposób związany z własnością, czy to poprzez bezpośrednią kreację, czy pośrednio poprzez wartości zwrotne. to Q & A Z C++ FAQ.

Bardziej drobnoziarnista kontrola czasu życia za każdym razem, gdy współdzielony wskaźnik jest kopiowany (np. jako argument funkcji), zasób, na który wskazuje, jest utrzymywany przy życiu. Zwykłe obiekty (Nie utworzone przez new, bezpośrednio przez Ciebie lub wewnątrz klasy zasobów) są niszczone, gdy wychodzą poza zakres.

 158
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:53

Istnieje wiele doskonałych odpowiedzi na to pytanie, w tym ważne przypadki użycia deklaracji forward, polimorfizmu itp. ale czuję, że część "duszy" twojego pytania nie odpowiada - mianowicie, co oznaczają różne składnie w Javie i C++.

Przyjrzyjmy się sytuacji porównując oba języki:

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

Najbliższy odpowiednik tego, to:

C++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

Zobaczmy alternatywny sposób C++:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

The najlepszym sposobem na myślenie o tym jest to, że -- mniej więcej -- Java (domyślnie) obsługuje wskaźniki do obiektów, podczas gdy C++ może obsługiwać wskaźniki do obiektów lub same obiekty. Istnieją od tego wyjątki - na przykład, jeśli deklarujesz "prymitywne" typy Javy, są to rzeczywiste wartości, które są kopiowane, a nie wskaźniki. Więc

Java:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

To powiedziawszy, używanie wskaźników niekoniecznie jest poprawnym lub złym sposobem radzenia sobie z rzeczami; jednak inne odpowiedzi obejmowały to / align = "left" / Ogólna idea jest jednak taka, że w C++ masz znacznie większą kontrolę nad żywotnością obiektów i nad tym, gdzie będą one żyć.

Zwróć uwagę na to, że konstrukt Object * object = new Object() jest w rzeczywistości tym, co jest najbliższe typowej semantyce Javy (lub C#).

 115
Author: Gerasimos R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-20 11:10:57

Innym dobrym powodem do używania wskaźników byłoby forward declarations . W wystarczająco dużym projekcie mogą naprawdę przyspieszyć czas kompilacji.

 73
Author: Burnt Toast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-05 05:38:46

Przedmowa

Java w niczym nie przypomina C++, w przeciwieństwie do hype. Java hype machine chciałby, abyś uwierzył, że ponieważ Java ma składnię podobną do C++, języki są podobne. Nic bardziej mylnego. Ta błędna informacja jest jednym z powodów, dla których programiści Javy przechodzą do C++ i używają składni podobnej do Javy bez zrozumienia implikacji ich kodu.

Dalej idziemy

Ale nie mogę zrozumieć, dlaczego mamy to zrobić w ten sposób. Ja bym Załóżmy, że ma związek z wydajnością i szybkością, ponieważ uzyskujemy bezpośredni dostęp do adres pamięci. Mam rację?

Wręcz przeciwnie. sterta jest znacznie wolniejsza niż sterta, ponieważ sterta jest bardzo prosta w porównaniu do sterty. Automatyczne zmienne magazynujące (aka zmienne stosu) mają wywoływane destruktory po wyjściu z zakresu. Na przykład:
{
    std::string s;
}
// s is destroyed here

Z drugiej strony, jeśli użyjesz dynamicznie przydzielonego wskaźnika, jego Destruktor musi zostać wywołany ręcznie. / Align = "center" bgcolor = "# e0ffe0 " / cesarz chin / / align = center /

{
    std::string* s = new std::string;
}
delete s; // destructor called

Nie ma to nic wspólnego ze składnią new rozpowszechnioną w C# i Javie. Są one wykorzystywane do zupełnie innych celów.

Korzyści z dynamicznej alokacji

1. Nie musisz z góry znać rozmiaru tablicy

Jednym z pierwszych problemów, na jakie napotyka wielu programistów C++, jest to, że akceptując dowolne dane wejściowe od użytkowników, można przydzielić tylko stały rozmiar dla zmiennej stosu. Nie można również zmienić rozmiaru tablic. Na przykład:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

Oczywiście, jeśli zamiast tego użyłeś std::string, std::string wewnętrznie zmienia rozmiar, więc nie powinno to być problemem. Ale zasadniczo rozwiązaniem tego problemu jest dynamiczna alokacja. Można przydzielić pamięć dynamiczną na podstawie danych wejściowych użytkownika, na przykład:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

Uwaga poboczna: jednym z błędów popełnianych przez wielu początkujących jest użycie tablice o zmiennej długości. To jest GNU rozszerzenie, a także jeden w Clang ponieważ odzwierciedlają wiele rozszerzeń GCC. Tak więc następujące int arr[n] nie należy na nich polegać.

Ponieważ sterta jest znacznie większa od stosu, można dowolnie przydzielać / realokować tyle pamięci, ile potrzebuje, podczas gdy stos ma pewne ograniczenia.

2. Tablice nie są wskaźnikami

Jak to jest korzyść, o którą prosisz? Odpowiedź stanie się jasna, gdy zrozumiesz zamieszanie / mit stojący za tablice i wskaźniki. Powszechnie przyjmuje się, że są one takie same, ale nie są. Mit ten wynika z faktu, że wskaźniki mogą być zapisywane tak jak tablice i z powodu rozpadu tablic na Wskaźniki na najwyższym poziomie w deklaracji funkcji. Jednak gdy tablica rozpada się na wskaźnik, wskaźnik traci swoją informację sizeof. Tak więc sizeof(pointer) poda rozmiar wskaźnika w bajtach, który zwykle wynosi 8 bajtów w systemie 64-bitowym.

Nie można przypisać do tablic, tylko zainicjalizować oni. Na przykład:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

Z drugiej strony, możesz robić co chcesz ze wskaźnikami. Niestety, ponieważ w Javie i C# rozróżnienie między wskaźnikami i tablicami jest Machane ręcznie, początkujący nie rozumieją różnicy.

3. Polimorfizm

Java i C# mają udogodnienia, które pozwalają traktować obiekty jako inne, na przykład za pomocą słowa kluczowego as. Więc jeśli ktoś chciał traktować obiekt Entity jako obiekt Player, jeden could do Player player = Entity as Player; jest to bardzo przydatne, jeśli zamierzasz wywoływać funkcje na jednorodnym kontenerze, który powinien dotyczyć tylko określonego typu. Funkcjonalność może być osiągnięta w podobny sposób poniżej:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

Więc powiedzmy, że gdyby tylko Trójkąty miały funkcję Rotate, byłby to błąd kompilatora, jeśli próbowałbyś wywołać ją na wszystkich obiektach klasy. Używając dynamic_cast, możesz symulować słowo kluczowe as. Aby było jasne, jeśli rzut nie powiedzie się, zwróci nieprawidłowy wskaźnik. Więc !test jest zasadniczo skrótem do sprawdzania, czy test jest NULL lub nieprawidłowym wskaźnikiem, co oznacza, że cast nie powiódł się.

Zalety zmiennych automatycznych

Po obejrzeniu wszystkich wspaniałych rzeczy, które może zrobić dynamiczna alokacja, prawdopodobnie zastanawiasz się, dlaczego nikt nie miałby nie używać dynamicznej alokacji cały czas? Powiedziałem ci już jeden powód, sterta jest powolna. A jeśli nie potrzebujesz całej tej pamięci, nie powinieneś jej nadużywać. Więc oto kilka wad w żadnej konkretnej kolejności:

  • On podatne na błędy. Ręczna alokacja pamięci jest niebezpieczna i podatna na wycieki. Jeśli nie jesteś biegły w użyciu debuggera lub valgrind (narzędzie wycieku pamięci), możesz wyciągnąć włosy z głowy. Na szczęście idiomy RAII i inteligentne wskaźniki łagodzą to trochę, ale musisz być zaznajomiony z praktykami takimi jak reguła trzech i reguła pięciu. Jest to wiele informacji do wzięcia, a początkujący, którzy albo nie wiedzą, albo nie dbają, wpadną w tę pułapkę.

  • Nie jest konieczne. W przeciwieństwie do Javy i C#, gdzie idiomatyczne jest używanie słowa kluczowego new wszędzie, w C++ powinieneś używać go tylko wtedy, gdy potrzebujesz. Często mówi się, że wszystko wygląda jak gwóźdź, jeśli masz młotek. Podczas gdy początkujący, którzy zaczynają z C++ boją się wskaźników i uczą się używać zmiennych stosu przez zwyczaj, programiści Java i C# zaczynają używając wskaźników bez zrozumienia tego! To dosłownie odepchnięcie niewłaściwej stopy. Musisz porzucić wszystko, co wiesz, ponieważ składnia to jedno, nauka języka to drugie.

1. (N)RVO - Aka, (Named) Return Value Optimization

Jedna optymalizacja, którą tworzy wiele kompilatorów, to rzeczy zwane elision i return value optimization . Te rzeczy mogą wyeliminować niepotrzebne kopiowania, co jest przydatne dla obiektów, które są bardzo duże, takie jak wektor zawierający wiele elementów. Zwykle powszechną praktyką jest używanie wskaźników do transferu własność zamiast kopiować duże obiekty do przenieść wokół nich. To doprowadziło do powstania semantyki ruchu i inteligentnych wskaźników.

Jeśli używasz wskaźników, (N)RVO nie występuje , a nie. Bardziej korzystne i mniej podatne na błędy jest wykorzystanie (N)RVO zamiast zwracania lub przekazywania wskaźników, jeśli martwisz się o optymalizację. Błąd może się zdarzyć, jeśli wywołujący funkcję jest odpowiedzialny za delete ing a dynamicznie przydzielany obiekt i takie. Śledzenie własności obiektu może być trudne, jeśli wskaźniki są przekazywane jak gorący ziemniak. Wystarczy użyć zmiennych stosu, ponieważ jest to prostsze i lepsze.

 62
Author: user3391320,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 16:28:47

C++ daje trzy sposoby przekazywania obiektu: przez wskaźnik, przez odniesienie i przez wartość. Java ogranicza cię do tego drugiego (jedynym wyjątkiem są prymitywne typy jak int, boolean itp.). Jeśli chcesz używać C++ nie tylko jako dziwnej zabawki, to lepiej poznaj różnicę między tymi trzema sposobami.

Java udaje, że nie ma takiego problemu jak " kto i kiedy powinien to zniszczyć?'. Odpowiedź brzmi: Śmieciarz, wielki i okropny. Niemniej jednak nie może zapewnić 100% ochrona przed wyciekami pamięci (tak, java can leak memory ). GC daje fałszywe poczucie bezpieczeństwa. Im większy SUV, tym dłuższa droga do ewakuatora.

C++ pozostawia cię twarzą w twarz z zarządzaniem cyklem życia obiektu. Cóż, istnieją sposoby radzenia sobie z tym (smart pointers family, QObject w Qt i tak dalej), ale żaden z nich nie może być używany w "fire and forget" sposób jak GC: powinieneś zawsze pamiętać o obsłudze pamięci. Nie tylko jeśli zależy ci na zniszczeniu obiektu, musisz również unikać niszczenia tego samego obiektu więcej niż raz.

Jeszcze się nie boisz? Ok: cykliczne odniesienia-zajmij się nimi sam, człowieku. I pamiętaj: Zabij każdy obiekt dokładnie raz, my środowiska uruchomieniowe C++ nie lubimy tych, którzy zadzierają ze zwłokami, zostawiają martwych w spokoju. Wracając do pytania.

Kiedy przekazujesz swój obiekt przez wartość, a nie przez wskaźnik lub odniesienie, kopiujesz obiekt (cały obiekt, niezależnie od tego, czy jest kilka bajtów lub ogromny zrzut bazy danych - jesteś na tyle sprytny, aby uniknąć tego ostatniego, prawda?) za każdym razem gdy robisz '='. I aby uzyskać dostęp do członków obiektu, używasz"."(kropka).

Kiedy przekazujesz swój obiekt za pomocą wskaźnika, kopiujesz tylko kilka bajtów (4 na systemach 32-bitowych, 8 na systemach 64-bitowych), a mianowicie - adres tego obiektu. Aby pokazać to wszystkim, używasz tego fantazyjnego operatora" ->", gdy uzyskujesz dostęp do członków. Możesz też użyć kombinacji ' * 'i'.'.

Kiedy używasz referencje, wtedy dostajesz wskaźnik, który udaje wartość. To wskaźnik, ale masz dostęp do członków przez".'.

I, aby jeszcze raz rozwalić twój umysł: gdy zadeklarujesz kilka zmiennych oddzielonych przecinkami ,to (uważaj na ręce):

  • typ jest podany każdemu
  • modyfikator wartości/wskaźnika/odniesienia jest indywidualny

Przykład:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
 21
Author: Kirill Gamazkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-17 15:55:16

W C++ obiekty przydzielone na stos (używając instrukcji Object object; w bloku) będą żyć tylko w zakresie, w którym są zadeklarowane. Gdy blok kodu zakończy wykonywanie, zadeklarowany obiekt zostanie zniszczony. Natomiast jeśli przydzielasz pamięć na stercie, używając Object* obj = new Object(), będą one żyć w stercie, dopóki nie wywołasz delete obj.

Chciałbym utworzyć obiekt na stercie, gdy chcę go używać nie tylko w bloku kodu, który go zadeklarował/przydzielił.

 19
Author: Karthik Kalyanasundaram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-04 09:48:26

Ale nie mogę zrozumieć, dlaczego mamy go używać w ten sposób?

Porównam, jak to działa wewnątrz ciała funkcji, jeśli użyjesz:

Object myObject;

Wewnątrz funkcji, twoja myObject zostanie zniszczona, gdy ta funkcja powróci. Jest to przydatne, jeśli nie potrzebujesz obiektu poza swoją funkcją. Ten obiekt zostanie umieszczony na bieżącym stosie wątków.

Jeśli napiszesz wewnątrz ciała funkcji:

 Object *myObject = new Object;

Wtedy instancja klasy Object wskazywana przez myObject nie otrzyma zniszczony po zakończeniu funkcji, a przydział jest na stercie.

Teraz, jeśli jesteś programistą Javy, to drugi przykład jest bliższy temu, jak działa alokacja obiektów w Javie. Ten wiersz: Object *myObject = new Object; jest odpowiednikiem java: Object myObject = new Object();. Różnica polega na tym, że w Javie myObject pobiera śmieci, podczas gdy w c++ nie zostanie uwolniony, musisz gdzieś jawnie wywołać 'delete myObject;' w przeciwnym razie wprowadzisz wycieki pamięci.

Od c++11 Można używać bezpiecznych sposobów dynamicznego alokacje: new Object, przechowując wartości w shared_ptr / unique_ptr.

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

Ponadto obiekty są bardzo często przechowywane w kontenerach, takich jak map-s lub vector-s, będą automatycznie zarządzać żywotnością obiektów.

 19
Author: marcinj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 13:49:25

Technicznie jest to problem alokacji pamięci, jednak tutaj są jeszcze dwa praktyczne aspekty tego. Ma to związek z dwiema rzeczami: 1) Scope, gdy zdefiniujesz obiekt bez wskaźnika, nie będziesz już mógł uzyskać do niego dostępu po bloku kodu, w którym jest zdefiniowany, podczas gdy jeśli zdefiniujesz wskaźnik z "new", możesz uzyskać do niego dostęp z dowolnego miejsca, w którym masz wskaźnik do tej pamięci, dopóki nie zadzwonisz" delete " na tym samym wskaźniku. 2) Jeśli chcesz przekazać argumenty do funkcji, chcesz przekazać wskaźnik lub odniesienie, aby być bardziej wydajnym. Gdy przekazujesz obiekt, obiekt jest kopiowany, jeśli jest to obiekt, który zużywa dużo pamięci, może to być pochłonięte przez procesor (np. kopiujesz wektor pełen danych). Kiedy przekazujesz wskaźnik, wszystko, co przekazujesz, to jedna int(w zależności od implementacji, ale większość z nich to jedna int).

Poza tym musisz zrozumieć, że "nowy" przydziela pamięć na stercie, która musi zostać uwolniona w pewnym momencie. Kiedy nie trzeba używać "nowego" proponuję używasz zwykłej definicji obiektu "na stosie".

 12
Author: in need of help,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-03 12:05:54

Cóż, główne pytanie brzmi dlaczego powinienem używać wskaźnika, a nie samego obiektu? i moja odpowiedź, nie powinieneś (prawie) nigdy używać wskaźnika zamiast obiektu, ponieważ C++ ma odniesienia , jest bezpieczniejsze niż wskaźniki i gwarantuje taką samą wydajność jak wskaźniki.

Kolejna rzecz, o której wspomniałeś w swoim pytaniu:

Object *myObject = new Object;
Jak to działa? Tworzy wskaźnik typu Object, przydziela pamięć do jednego obiektu i wywołuje konstruktor domyślny, Dźwięki dobrze, prawda? Ale w rzeczywistości nie jest tak dobrze, jeśli dynamicznie przydzielana pamięć( używane słowo kluczowe new), musisz również zwolnić pamięć ręcznie, co oznacza, że w kodzie powinieneś mieć:
delete myObject;

To wywołuje destructor i zwalnia pamięć, wygląda łatwo, jednak w dużych projektach może być trudne do wykrycia, czy jeden wątek zwalnia pamięć, ale w tym celu można spróbować współdzielone wskaźniki, te nieznacznie zmniejsza wydajność, ale znacznie łatwiej jest pracować z nimi. oni.


A teraz pewne wprowadzenie jest skończone i wrócić do pytania.

Możesz używać wskaźników zamiast obiektów, aby uzyskać lepszą wydajność podczas przesyłania danych między funkcjami.

Spójrz, masz std::string (jest to również obiekt) i zawiera naprawdę dużo danych, na przykład big XML, teraz musisz je przeanalizować, ale do tego masz funkcję void foo(...), która może być zadeklarowana na różne sposoby:

  1. void foo(std::string xml); W takim przypadku skopiujesz wszystkie dane z twoja zmienna do stosu funkcji, zajmuje trochę czasu, więc twoja wydajność będzie niska.
  2. void foo(std::string* xml); W tym przypadku przekazujemy wskaźnik do obiektu, z taką samą prędkością jak przekazujemy zmienną size_t, jednak ta deklaracja jest podatna na błędy, ponieważ można przekazać wskaźnik NULL lub nieprawidłowy wskaźnik. Wskaźniki zwykle używane w C, ponieważ nie mają referencji.
  3. void foo(std::string& xml); Tutaj przekazujesz referencję, w zasadzie jest to to samo co przekazujący wskaźnik, ale kompilator robi pewne rzeczy i nie możesz przekazać invalid reference (w rzeczywistości można wytworzyć sytuację z invalid reference, ale jest to oszustwo kompilatora).
  4. void foo(const std::string* xml); Tutaj jest taki sam jak drugi, tylko wartość wskaźnika nie może być zmieniona.
  5. void foo(const std::string& xml); Tutaj jest to samo co trzeci, ale wartość obiektu nie może zostać zmieniona.

Co jeszcze chcę wspomnieć, możesz użyć tych 5 sposobów przekazywania danych bez względu na wybrany sposób alokacji(z new lub regularnym ).


Kolejna rzecz do wspomnij, że gdy tworzysz obiekt w zwykły, przydzielasz pamięć w stosie, ale gdy tworzysz go za pomocą new przydzielasz stertę. O wiele szybsze jest przydzielanie stosu, ale jest to trochę małe dla naprawdę dużych tablic danych, więc jeśli potrzebujesz dużego obiektu, powinieneś użyć sterty, ponieważ możesz uzyskać przepełnienie stosu, ale zazwyczaj ten problem jest rozwiązany za pomocą kontenerów STL i pamiętaj std::string jest również kontenerem, Niektórzy o tym zapomnieli :)

]}
 6
Author: ST3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-08 10:48:02

Załóżmy, że masz class A które zawierają class B Kiedy chcesz wywołać jakąś funkcję class B poza class A po prostu uzyskasz wskaźnik do tej klasy i możesz robić co chcesz, a to również zmieni kontekst class B w twoim class A {7]}

Ale uważaj na dynamiczny obiekt

 5
Author: Quest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-03 12:02:35

Istnieje wiele korzyści z używania wskaźników do obiektu -

  1. Efektywność (jak już zauważyłeś). Przekazywanie obiektów do funkcje oznaczają tworzenie nowych kopii obiektu.
  2. Praca z obiektami z bibliotek stron trzecich. Jeśli twój obiekt należy do kodu strony trzeciej i autorzy zamierzają używać swoich obiektów tylko za pomocą wskaźników (bez konstruktorów kopiujących itp.) obiekt używa wskaźników. Przekazywanie wartości może powodować problemy. (Głębokie Kopiuj / płytkie problemy z kopiowaniem).
  3. jeśli obiekt jest właścicielem zasobu i chcesz, aby własność nie była powiązana z innymi obiektami.
 5
Author: Rohit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-03 12:18:41

Jest to już omówione, ale w Javie wszystko jest wskaźnikiem. Nie rozróżnia między przydziałem stosu i sterty (wszystkie obiekty są przydzielane na stercie), więc nie zdajesz sobie sprawy, że używasz wskaźników. W C++, można mieszać te dwa, w zależności od wymagań pamięci. Wydajność i wykorzystanie pamięci jest bardziej deterministyczne w C++ (duh).

 3
Author: cmollis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-03 21:17:41
Object *myObject = new Object;

Spowoduje utworzenie odniesienia do obiektu (na stercie), który musi zostać usunięty jawnie, aby uniknąć wycieku pamięci .

Object myObject;

Spowoduje utworzenie obiektu (myObject) typu automatic (na stosie), który zostanie automatycznie usunięty, gdy obiekt(myObject) wyjdzie poza zakres.

 3
Author: Palak Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-15 17:07:16

Wskaźnik bezpośrednio odwołuje się do lokalizacji pamięci obiektu. Java nie ma nic takiego. Java posiada odniesienia, które odwołują się do lokalizacji obiektu za pomocą tabel skrótów. Z tymi referencjami nie można zrobić nic podobnego do arytmetyki wskaźników w Javie.

Aby odpowiedzieć na twoje pytanie, to tylko twoje preferencje. Wolę używać składni podobnej do Javy.

 1
Author: RioRicoRick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-03 21:15:29

Ty nie powinieneś . Ludzie (wielu ludzi, niestety) piszą to z niewiedzy.

Czasami dynamiczna alokacja ma swoje miejsce, ale w podanych przykładach jest błędna .

Jeśli chcesz myśleć o efektywności, to jest to gorsze , ponieważ wprowadza indrection bez dobrego powodu. Ten rodzaj programowania jest wolniejszy i bardziej podatny na błędy .

 1
Author: Lightness Races in Orbit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-26 12:27:58

Ze wskaźnikami ,

  • Może bezpośrednio rozmawiać z pamięcią.

  • Może zapobiec wielu wyciekom pamięci programu poprzez manipulowanie wskaźnikami.

 0
Author: lasan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-30 12:59:40

Jednym z powodów używania wskaźników jest interfejs z funkcjami C. Innym powodem jest zapisywanie pamięci; na przykład: zamiast przekazywać obiekt, który zawiera dużo danych i ma intensywny procesor kopiowania konstruktor do funkcji, po prostu przekazać wskaźnik do obiektu, oszczędzając pamięć i szybkość, zwłaszcza jeśli jesteś w pętli, jednak odniesienie byłoby lepsze w tym przypadku, chyba że używasz tablicy w stylu C.

 0
Author: theo2003,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-11 20:03:39

W obszarach , w których wykorzystanie pamięci jest na najwyższym poziomie, przydatne są wskaźniki. Na przykład rozważ algorytm minimax, w którym tysiące węzłów będą generowane za pomocą rekurencyjnej procedury, a następnie wykorzystaj je do oceny następnego najlepszego ruchu w grze, możliwość dealokacji lub resetowania (jak w inteligentnych wskaźnikach) znacznie zmniejsza zużycie pamięci. Podczas gdy zmienna bez wskaźnika nadal zajmuje przestrzeń, dopóki rekurencyjne wywołanie nie zwróci wartości.

 0
Author: seccpur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-18 17:11:52

Dołączę jeden ważny przypadek użycia wskaźnika. Gdy przechowujesz jakiś obiekt w klasie bazowej, ale może on być polimorficzny.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

Więc w tym przypadku nie możesz zadeklarować bObj jako obiektu bezpośredniego, musisz mieć wskaźnik.

 0
Author: user18853,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 10:25:35

"konieczność jest matką wynalazku." Najważniejszą różnicą, na którą chciałbym zwrócić uwagę, jest wynik mojego własnego doświadczenia w kodowaniu. Czasami trzeba przekazać obiekty do funkcji. W takim przypadku, jeśli twój obiekt jest bardzo dużej klasy, przekazanie go jako obiektu skopiuje jego stan (czego możesz nie chcieć ..I może być duży narzut), co powoduje narzut kopiującego obiektu .natomiast wskaźnik ma stały rozmiar 4-bajtowy (przy założeniu 32 bitów). Inne powody są już wymienione powyżej...

 0
Author: sandeep bisht,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 16:29:23

Jest już wiele doskonałych odpowiedzi, ale podam jeden przykład:

Mam prostą klasę przedmiotów:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

Robię wektor, aby trzymać ich kilka.

std::vector<Item> inventory;

Tworzę milion obiektów i przesuwam je z powrotem na wektor. Sortuję wektor po nazwie, a następnie wykonuję proste iteracyjne wyszukiwanie binarne dla konkretnej nazwy elementu. Testuję program, a jego wykonanie zajmuje ponad 8 minut. Potem zmieniam wektor inwentarza jak więc:

std::vector<Item *> inventory;

...i utworzyć mój milion obiektów za pomocą nowych. Jedyne zmiany, które wprowadzam w kodzie, to użycie wskaźników do elementów, z wyjątkiem pętli, którą dodaję do czyszczenia pamięci na końcu. Ten program działa w mniej niż 40 sekund, lub lepiej niż 10-krotne zwiększenie prędkości. EDIT: kod jest na http://pastebin.com/DK24SPeW Z optymalizacjami kompilatora pokazuje tylko wzrost 3.4 x na maszynie, na której właśnie testowałem, który jest nadal znaczny.

 -4
Author: Darren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-06 15:20:03