Istnieją dwie powszechnie stosowane techniki alokacji pamięci: alokacja automatyczna i alokacja dynamiczna. Zwykle dla każdego istnieje odpowiedni region pamięci: stos i sterta.
Stos
Stos zawsze przydziela pamięć w sposób sekwencyjny. Może to zrobić, ponieważ wymaga zwolnienia pamięci w odwrotnej kolejności (pierwsze wejście, ostatnie wyjście: FILO). Jest to technika alokacji pamięci dla zmiennych lokalnych w wielu językach programowania. Jest bardzo, bardzo szybki, ponieważ wymaga minimalnej księgowości, a następny adres do przydzielenia jest domyślny.
W C ++ nazywa się to automatycznym magazynowaniem, ponieważ magazyn jest zgłaszany automatycznie na końcu zakresu. Po {}
zakończeniu wykonywania bieżącego bloku kodu (ograniczonego za pomocą ), pamięć dla wszystkich zmiennych w tym bloku jest automatycznie gromadzona. Jest to także moment, w którym przywoływane są destruktory w celu oczyszczenia zasobów.
Sterta
Sterta pozwala na bardziej elastyczny tryb alokacji pamięci. Księgowość jest bardziej złożona, a alokacja wolniejsza. Ponieważ nie ma niejawnego punktu zwolnienia, musisz zwolnić pamięć ręcznie, używając delete
lub delete[]
( free
w C). Jednak brak niejawnego punktu zwolnienia jest kluczem do elastyczności stosu.
Powody korzystania z alokacji dynamicznej
Nawet jeśli korzystanie ze sterty jest wolniejsze i potencjalnie prowadzi do wycieków pamięci lub fragmentacji pamięci, istnieją bardzo dobre przypadki użycia dla alokacji dynamicznej, ponieważ jest ona mniej ograniczona.
Dwa kluczowe powody korzystania z alokacji dynamicznej:
Nie wiesz, ile pamięci potrzebujesz w czasie kompilacji. Na przykład podczas odczytywania pliku tekstowego do łańcucha zwykle nie wiesz, jaki rozmiar ma ten plik, więc nie możesz zdecydować, ile pamięci ma zostać przydzielone, dopóki nie uruchomisz programu.
Chcesz przydzielić pamięć, która pozostanie po opuszczeniu bieżącego bloku. Na przykład możesz chcieć napisać funkcję, string readfile(string path)
która zwraca zawartość pliku. W takim przypadku nawet jeśli stos może pomieścić całą zawartość pliku, nie można wrócić z funkcji i zachować przydzielonego bloku pamięci.
Dlaczego dynamiczna alokacja jest często niepotrzebna
W C ++ istnieje zgrabna konstrukcja zwana destruktorem . Ten mechanizm pozwala zarządzać zasobami, dopasowując czas życia zasobu do czasu życia zmiennej. Ta technika nazywa się RAII i jest wyróżnikiem C ++. „Zawija” zasoby w obiekty. std::string
jest doskonałym przykładem. Ten fragment kodu:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
faktycznie przydziela zmienną ilość pamięci. std::string
Przydziela pamięć obiektów za pomocą sterty i uwalnia go w jego destruktora. W takim przypadku nie trzeba ręcznie zarządzać zasobami, a korzyści z dynamicznej alokacji pamięci są nadal możliwe.
W szczególności oznacza to, że w tym fragmencie:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
istnieje niepotrzebna dynamiczna alokacja pamięci. Program wymaga więcej pisania (!) I stwarza ryzyko zapomnienia o zwolnieniu pamięci. Robi to bez widocznych korzyści.
Dlaczego warto korzystać z automatycznego przechowywania tak często, jak to możliwe
Zasadniczo ostatni akapit podsumowuje. Używanie automatycznego przechowywania tak często, jak to możliwe, sprawia, że twoje programy:
- szybciej pisać;
- szybciej po uruchomieniu;
- mniej podatne na wycieki pamięci / zasobów.
Punkty bonusowe
W cytowanym pytaniu pojawiają się dodatkowe obawy. W szczególności następująca klasa:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Jest o wiele bardziej ryzykowny w użyciu niż następujący:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
Powodem jest to, że std::string
poprawnie definiuje konstruktor kopii. Rozważ następujący program:
int main ()
{
Line l1;
Line l2 = l1;
}
Korzystając z oryginalnej wersji, ten program najprawdopodobniej ulegnie awarii, ponieważ używa delete
tego samego ciągu dwukrotnie. Korzystając ze zmodyfikowanej wersji, każda Line
instancja będzie posiadać własną instancję łańcuchową , każda z własną pamięcią i obie zostaną zwolnione na końcu programu.
Inne notatki
Szerokie zastosowanie RAII jest uważane za najlepszą praktykę w C ++ ze wszystkich powyższych powodów. Istnieje jednak dodatkowa korzyść, która nie jest od razu oczywista. Zasadniczo jest lepszy niż suma jego części. Cały mechanizm się komponuje . Skaluje się.
Jeśli użyjesz Line
klasy jako elementu konstrukcyjnego:
class Table
{
Line borders[4];
};
Następnie
int main ()
{
Table table;
}
przydziela cztery std::string
instancje, cztery Line
instancje, jedną Table
instancję i całą zawartość łańcucha, a wszystko jest automatycznie uwalniane automatycznie .
Monster
wypluwa aTreasure
doWorld
momentu śmierci. W swojejDie()
metodzie dodaje skarb do świata. Musi użyć goworld->Add(new Treasure(/*...*/))
w innym, aby zachować skarb po jego śmierci. Alternatywami sąshared_ptr
(może być nadmiar),auto_ptr
(zły semantyczny termin przeniesienia własności), przekazywanie według wartości (marnotrawstwo) imove
+unique_ptr
(jeszcze powszechnie nie wdrażane).Ponieważ stos jest szybszy i szczelny
W C ++ potrzeba tylko jednej instrukcji, aby przydzielić miejsce - na stosie - dla każdego lokalnego obiektu zakresu w danej funkcji i nie można przeciekać żadnej z tych pamięci. Ten komentarz zamierzał (lub powinien był zamierzyć) powiedzieć coś w stylu „użyj stosu, a nie stosu”.
źródło
int x; return &x;
Powód jest skomplikowany.
Po pierwsze, C ++ nie jest śmieciami. Dlatego dla każdego nowego musi istnieć odpowiednie usunięcie. Jeśli nie umieścisz tego usuwania, oznacza to wyciek pamięci. Teraz w przypadku prostego przypadku takiego:
To jest proste. Ale co się stanie, jeśli „Do stuff” zgłosi wyjątek? Ups: wyciek pamięci. Co się stanie, jeśli problem „Zrób rzeczy”
return
wcześnie? Ups: wyciek pamięci.I to jest w najprostszym przypadku . Jeśli zdarzy ci się zwrócić komuś ten ciąg, teraz musi go usunąć. A jeśli przekażą to jako argument, to czy osoba otrzymująca go musi go usunąć? Kiedy powinni je usunąć?
Lub możesz po prostu to zrobić:
Nie
delete
. Obiekt został utworzony na „stosie” i zostanie zniszczony, gdy znajdzie się poza zasięgiem. Możesz nawet zwrócić obiekt, przenosząc jego zawartość do funkcji wywołującej. Możesz przekazać obiekt do funkcji (zwykle jako odwołanie lub stałe odwołanie:void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
itd.).Wszystko bez
new
idelete
. Nie ma wątpliwości, kto jest właścicielem pamięci lub kto jest odpowiedzialny za jej usunięcie. Jeśli zrobisz:Zrozumiałe jest, że
otherString
posiada kopię danych zsomeString
. To nie jest wskaźnik; jest to osobny obiekt. Może się zdarzyć, że mają tę samą zawartość, ale możesz zmienić jedną bez wpływu na drugą:Widzisz pomysł?
źródło
main()
, istnieje przez czas trwania programu, nie można go łatwo utworzyć na stosie ze względu na sytuację, a wskaźniki do niego są przekazywane do wszystkich funkcji wymagających dostępu do niego , czy może to spowodować wyciek w przypadku awarii programu, czy też byłoby bezpieczne? Zakładam to drugie, ponieważ system operacyjny zwalniający całą pamięć programu powinien również logicznie zwolnić go, ale nie chcę niczego zakładać, jeśli chodzi o tonew
.Obiekty utworzone przez
new
muszą w końcudelete
nie przeciekać. Destruktor nie zostanie wywołany, pamięć nie zostanie uwolniona, cały czas. Ponieważ C ++ nie ma śmieci, jest to problem.Obiekty tworzone przez wartość (tj. Na stosie) automatycznie umierają, gdy wykraczają poza zakres. Wywołanie destruktora jest wstawiane przez kompilator, a pamięć jest automatycznie zwalniana po powrocie funkcji.
Inteligentne wskaźniki, takie jak
unique_ptr
,shared_ptr
rozwiązują wiszący problem odniesienia, ale wymagają dyscypliny kodowania i mają inne potencjalne problemy (kopiowanie, pętle odniesienia itp.).Również w mocno wielowątkowych scenariuszach
new
istnieje spór między wątkami; nadużycie może mieć wpływ na wydajnośćnew
. Tworzenie obiektu stosu jest z definicji wątkiem lokalnym, ponieważ każdy wątek ma swój własny stos.Wadą obiektów wartości jest to, że umierają one po powrocie funkcji hosta - nie można przekazać odwołania do tych z powrotem do obiektu wywołującego, tylko poprzez kopiowanie, zwracanie lub przenoszenie według wartości.
źródło
new
muszą w końcudelete
nie przeciekać”. - jeszcze gorzej,new[]
należy je dopasowaćdelete[]
, a zachowanie jest niezdefiniowane, jeśli użyjeszdelete
new[]
pamięci lubdelete[]
new
pamięci -ed - bardzo niewiele kompilatorów ostrzega o tym (niektóre narzędzia, takie jak Cppcheck, robią to, kiedy mogą).źródło
malloc()
ani jego przyjaciół w celu przydzielenia wymaganej pamięci. Jednak stos nie może zwolnić żadnego elementu w stosie, jedynym sposobem na zwolnienie pamięci stosu jest odwijanie z góry stosu.Widzę, że pominięto kilka ważnych powodów, dla których należy robić jak najmniej nowości:
Operator
new
ma niedeterministyczny czas wykonaniaWywołanie
new
może, ale nie musi, spowodować, że system operacyjny przydzieli nową fizyczną stronę procesowi. Może to być dość powolne, jeśli robisz to często. Lub może mieć już przygotowaną odpowiednią lokalizację pamięci, nie wiemy. Jeśli Twój program musi mieć spójny i przewidywalny czas wykonania (np. W systemie czasu rzeczywistego lub symulacji gry / fizyki), musisz unikaćnew
krytycznych pętli czasowych.Operator
new
to niejawna synchronizacja wątkówTak, słyszałeś mnie, twój system operacyjny musi się upewnić, że tabele stron są spójne i dlatego takie wywołanie
new
spowoduje, że Twój wątek uzyska ukrytą blokadę mutex. Jeśli konsekwentnie dzwonisznew
z wielu wątków, w rzeczywistości szeregujesz swoje wątki (zrobiłem to z 32 procesorami, z których każdy uderzył onew
kilkaset bajtów, ouch! To była królewska pita do debugowania)Pozostałe odpowiedzi, takie jak powolność, fragmentacja, podatność na błędy itp. Zostały już wspomniane w innych odpowiedziach.
źródło
void * someAddress = ...; delete (T*)someAddress
mlock()
lub coś podobnego. Jest tak, ponieważ w systemie może brakować pamięci i nie ma gotowych stron pamięci fizycznej dla stosu, więc system operacyjny może potrzebować zamienić lub zapisać niektóre pamięci podręczne (wyczyścić brudną pamięć) na dysku, zanim będzie można kontynuować wykonywanie.Pre-C ++ 17:
Ponieważ jest podatny na subtelne wycieki, nawet jeśli wynik zostanie zawinięty w inteligentny wskaźnik .
Rozważ „ostrożnego” użytkownika, który pamięta zawijanie obiektów w inteligentne wskaźniki:
Ten kod jest niebezpieczne, ponieważ nie ma żadnej gwarancji, że albo
shared_ptr
jest zbudowany przed alboT1
alboT2
. Stąd, jeśli jedennew T1()
lubnew T2()
nie powiedzie się po drugiej powiedzie, to pierwszy obiekt będzie przeciekał ponieważ nieshared_ptr
istnieje, aby zniszczyć i zwalnianie go.Rozwiązanie: użyj
make_shared
.Post-C ++ 17:
Nie stanowi to już problemu: C ++ 17 nakłada ograniczenie na kolejność tych operacji, w tym przypadku zapewniając, że po każdym wywołaniunew()
musi natychmiast nastąpić konstrukcja odpowiedniego inteligentnego wskaźnika, bez żadnych innych operacji pomiędzy. Oznacza to, że do czasunew()
wywołania drugiego jest zagwarantowane, że pierwszy obiekt został już owinięty w inteligentny wskaźnik, zapobiegając w ten sposób wyciekom w przypadku zgłoszenia wyjątku.Barry bardziej szczegółowe objaśnienie nowego porządku oceny wprowadzonego przez C ++ 17 zostało dostarczone przez Barry'ego w innej odpowiedzi .Dzięki @Remy Lebeau za zwrócenie uwagi, że nadal jest to problem w C ++ 17 (choć mniej):
shared_ptr
konstruktor może nie przydzielić bloku kontrolnego i wyrzucić, w którym to przypadku przekazany do niego wskaźnik nie jest usuwany.Rozwiązanie: użyj
make_shared
.źródło
new
powiedzie, a następnie następnashared_ptr
konstrukcja się nie powiedzie.std::make_shared()
też by to rozwiązałshared_ptr
konstruktor przydziela pamięć blokowi kontrolnemu, który przechowuje wspólny wskaźnik i separator, więc tak, teoretycznie może wyrzucić błąd pamięci. Tylko konstruktory kopiujące, przenoszące i aliasingowe nie rzucają.make_shared
przydziela współdzielony obiekt w samym bloku sterującym, więc jest tylko 1 przydział zamiast 2.W dużej mierze jest to ktoś, kto podnosi swoje słabości do ogólnej zasady. Nie ma nic złego per se z tworzenia obiektów za pomocą
new
operatora. Argumentuje się za tym, że musisz to robić z pewną dyscypliną: jeśli tworzysz obiekt, musisz upewnić się, że zostanie zniszczony.Najłatwiej to zrobić, tworząc obiekt w automatycznym magazynie, więc C ++ wie, jak go zniszczyć, gdy przekroczy zakres:
Teraz zauważ, że kiedy spadniesz z tego bloku po nawiasie końcowym,
foo
jest poza zasięgiem. C ++ wywoła dla ciebie swój dtor automatycznie. W przeciwieństwie do Javy, nie musisz czekać, aż GC go znajdzie.Czy napisałeś?
chciałbyś wyraźnie to dopasować
lub jeszcze lepiej, przydziel swój
File *
„inteligentny wskaźnik”. Jeśli nie będziesz ostrożny, może to prowadzić do wycieków.Sama odpowiedź błędnie zakłada, że jeśli nie użyjesz
new
, nie przydzielisz na stos; tak naprawdę w C ++ tego nie wiesz. Wiesz co najwyżej, że niewielka ilość pamięci, powiedzmy jeden wskaźnik, jest z pewnością przydzielona na stos. Zastanów się jednak, czy implementacja pliku jest podobnanastępnie
FileImpl
będzie nadal być alokowane na stosie.I tak, lepiej upewnij się, że masz
również w klasie; bez niego wycieknie pamięć ze sterty, nawet jeśli najwyraźniej w ogóle nie przydzielono jej na sterty.
źródło
new
per se , ale jeśli spojrzysz na oryginalny kod, do którego odnosi się komentarz,new
jest on nadużywany. Kod jest napisany tak, jakby to był Java lub C #, gdzienew
jest używany dla praktycznie każdej zmiennej, gdy na stosie jest o wiele więcej sensu.new
. Mówi, że jeśli mają wybór między automatycznym i dynamicznej alokacji pamięci, użyj automatycznego zapisu.new
, ale jeśli używaszdelete
, robisz to źle!new()
nie powinien być używany tak mało, jak to możliwe. Należy go stosować tak ostrożnie, jak to możliwe. I należy go używać tak często, jak to konieczne, podyktowane pragmatyzmem.Przydział obiektów na stosie, polegający na ich niejawnym zniszczeniu, jest prostym modelem. Jeśli wymagany zakres obiektu pasuje do tego modelu, nie ma potrzeby korzystania
new()
z powiązanegodelete()
i sprawdzania wskaźników NULL. W przypadku dużej ilości alokacji obiektów krótkotrwałych na stosie należy zmniejszyć problemy z fragmentacją sterty.Jeśli jednak czas życia obiektu musi wykraczać poza obecny zakres,
new()
to właściwa odpowiedź. Upewnij się tylko, że zwracasz uwagę na to, kiedy i jak wywołujesz,delete()
oraz na możliwości wskaźników NULL, używając usuniętych obiektów i wszystkich innych gotch, które pochodzą z użyciem wskaźników.źródło
const
odwołanie lub wskaźnik…?make_shared/_unique
jest to użyteczne) odbiorca nigdy nie musinew
lubdelete
. W tej odpowiedzi brakuje prawdziwych punktów: (A) C ++ zapewnia takie rzeczy, jak RVO, semantyka przenoszenia i parametry wyjściowe - co często oznacza, że obsługa tworzenia obiektów i wydłużania czasu życia poprzez zwracanie dynamicznie przydzielanej pamięci staje się niepotrzebna i nieostrożna. (B) Nawet w sytuacjach, w których wymagany jest dynamiczny przydział, stdlib zapewnia opakowania RAII, które odciążają użytkownika od brzydkich wewnętrznych szczegółów.Gdy używasz nowego, obiekty są przydzielane do sterty. Zwykle jest używany, gdy spodziewasz się rozszerzenia. Kiedy deklarujesz obiekt taki jak:
jest umieszczony na stosie.
Zawsze będziesz musiał wywołać kill na obiekcie, który umieściłeś na stosie z nowym. Otwiera to możliwość wycieków pamięci. Obiekty umieszczone na stosie nie są podatne na wycieki pamięci!
źródło
std::string
lubstd::map
, tak, bystry wgląd. Moją początkową reakcją było „ale także bardzo często oddzielenie czasu życia obiektu od zakresu tworzenia kodu”, ale tak naprawdę zwracanie wartości lub akceptowanie wartości o charakterze wywołującym przezconst
odwołanie lub wskaźnik jest do tego lepsze, z wyjątkiem sytuacji, gdy występuje „ekspansja” też. Istnieją jednak inne zastosowania dźwięku, takie jak metody fabryczne ...Jednym z godnych uwagi powodów, aby uniknąć nadużywania sterty, jest wydajność - szczególnie związana z wydajnością domyślnego mechanizmu zarządzania pamięcią używanego przez C ++. Podczas gdy alokacja może być dość szybka w trywialnym przypadku, robienie dużej ilości
new
idelete
na obiektach o niejednorodnym rozmiarze bez ścisłego porządku prowadzi nie tylko do fragmentacji pamięci, ale także komplikuje algorytm alokacji i może całkowicie zniszczyć wydajność w niektórych przypadkach.Jest to problem, który pule pamięci zostały stworzone do rozwiązania, co pozwala złagodzić nieodłączne wady tradycyjnych implementacji sterty, jednocześnie umożliwiając korzystanie ze sterty w razie potrzeby.
Jeszcze lepiej, aby całkowicie uniknąć problemu. Jeśli możesz umieścić go na stosie, zrób to.
źródło
Myślę, że plakat miał
You do not have to allocate everything on the
heap
raczej na myśli niżstack
.Zasadniczo obiekty są alokowane na stosie (jeśli pozwala na to rozmiar obiektu, oczywiście) ze względu na niski koszt alokacji stosu, a nie alokację opartą na stercie, która wymaga dość pracy ze strony alokatora, i dodaje gadatliwości, ponieważ wtedy musisz zarządzać danymi przydzielonymi na stercie.
źródło
Zwykle nie zgadzam się z pomysłem użycia nowego „za dużo”. Chociaż użycie nowego plakatu w klasach systemowych przez pierwotnego plakatu jest nieco niedorzeczne. (
int *i; i = new int[9999];
? naprawdę?int i[9999];
jest znacznie jaśniejszy.) Myślę, że to właśnie dostało kozę komentatora.Podczas pracy z obiektami systemowymi bardzo rzadko potrzeba więcej niż jednego odwołania do dokładnie tego samego obiektu. Dopóki wartość jest taka sama, tylko to się liczy. A obiekty systemowe zwykle nie zajmują dużo miejsca w pamięci. (jeden bajt na znak, w ciągu). A jeśli tak, biblioteki powinny być tak zaprojektowane, aby uwzględniały to zarządzanie pamięcią (jeśli są dobrze napisane). W takich przypadkach (wszystkie oprócz jednej lub dwóch wiadomości w jego kodzie) nowe są praktycznie bezcelowe i służą jedynie wprowadzeniu zamieszania i potencjalnym błędom.
Kiedy jednak pracujesz z własnymi klasami / obiektami (np. Klasą Line oryginalnego plakatu), musisz sam zacząć myśleć o takich kwestiach, jak pamięć, trwałość danych itp. W tym momencie dopuszczenie wielu odwołań do tej samej wartości jest nieocenione - pozwala na konstrukcje takie jak listy połączone, słowniki i wykresy, w których wiele zmiennych musi mieć nie tylko tę samą wartość, ale odwoływać się dokładnie do tego samego obiektu w pamięci. Jednak klasa Line nie ma żadnego z tych wymagań. Tak więc kod oryginalnego plakatu nie ma absolutnie żadnej potrzeby
new
.źródło
When you're working with your own classes/objects
... często nie masz ku temu powodu! Niewielka część pytań dotyczy szczegółów projektowania pojemników przez wykwalifikowanych programistów. W przeciwieństwie, przygnębiające odsetek są o pomieszaniu początkujących, którzy nie znają stdlib istnieje - albo aktywnie danego zadania w okropnych „programowania” „kursy”, gdzie nauczyciel domaga się bezsensownie wyważać otwartych drzwi - przed Oni nawet dowiedziałem się, czym jest koło i dlaczego działa. Promując bardziej abstrakcyjną alokację, C ++ może uchronić nas przed niekończącym się „segfaultem C z połączoną listą”; proszę, pozwólmy temu .int *i; i = new int[9999];
? naprawdę?int i[9999];
jest o wiele jaśniejsze.)„ Tak, jest jaśniejsze, ale aby zagrać w adwokata diabła, ten typ niekoniecznie jest złym argumentem. Z elementami 9999 mogę sobie wyobrazić ciasno osadzony system, który nie ma wystarczającego stosu dla elementów 9999: 9999 x 4 bajty to ~ 40 kB, x 8 ~ 80 kB. Dlatego takie systemy mogą wymagać użycia alokacji dynamicznej, zakładając, że implementują ją przy użyciu alternatywnej pamięci. Może to jednak uzasadniać dynamiczną alokację, a nienew
;vector
byłby prawdziwy dylemat w tej sprawiestd::make_unique<int[]>()
oczywiście).Dwa powody:
delete
później, w przeciwnym razie spowoduje to wyciek pamięci.źródło
new
jest nowygoto
.Przypomnij sobie, dlaczego
goto
jest tak oczerniany: chociaż jest to potężne narzędzie niskiego poziomu do kontroli przepływu, ludzie często używali go w niepotrzebnie skomplikowanych sposobach, które utrudniały śledzenie kodu. Ponadto najbardziej przydatne i najłatwiejsze do odczytania wzorce zostały zakodowane w ustrukturyzowanych instrukcjach programowania (np.for
Lubwhile
); ostatecznym efektem jest to, że kod, w którymgoto
jest odpowiedni sposób, jest raczej rzadki, jeśli masz ochotę pisaćgoto
, prawdopodobnie robisz rzeczy źle (chyba że naprawdę wiesz, co robisz).new
jest podobny - jest często używany, aby uczynić rzeczy niepotrzebnie skomplikowanymi i trudniejszymi do odczytania, a najbardziej przydatne wzorce użytkowania, które można zakodować, zostały zakodowane w różnych klasach. Ponadto, jeśli chcesz użyć nowych wzorców użycia, dla których nie ma jeszcze klas standardowych, możesz napisać własne klasy, które je kodują!Twierdziłbym nawet, że
new
jest gorzej niżgoto
ze względu na potrzebę parowanianew
idelete
oświadczeń.Na przykład
goto
, jeśli kiedykolwiek uważasz, że musisz użyćnew
, prawdopodobnie robisz coś źle - zwłaszcza jeśli robisz to poza wdrożeniem klasy, której celem w życiu jest zawarcie wszelkich dynamicznych alokacji, które musisz zrobić.źródło
Głównym powodem jest to, że obiekty na stercie są zawsze trudne w użyciu i zarządzaniu niż proste wartości. Pisanie łatwego do odczytania i utrzymania kodu jest zawsze priorytetem każdego poważnego programisty.
Innym scenariuszem jest używana przez nas biblioteka, która zapewnia semantykę wartości i sprawia, że alokacja dynamiczna jest niepotrzebna.
Std::string
jest dobrym przykłademJednak w przypadku kodu obiektowego konieczne jest użycie wskaźnika - co oznacza użycie
new
go wcześniej. Aby uprościć złożoność zarządzania zasobami, mamy dziesiątki narzędzi, które sprawiają, że jest to tak proste, jak to możliwe, takie jak inteligentne wskaźniki. Paradygmat oparty na obiektach lub paradygmat ogólny zakłada semantykę wartości i wymaga mniej lub wcalenew
, tak jak napisano w innych plakatach.Tradycyjne wzorce projektowe, zwłaszcza te wspomniane w książce GoF ,
new
często używają , ponieważ są typowym kodem OO.źródło
For object oriented code, using a pointer [...] is a must
: bzdury . Jeśli dewaluujesz „OO”, odnosząc się tylko do małego podzbioru, polimorfizm - także nonsens: referencje również działają.[pointer] means use new to create it beforehand
: szczególnie bzdury : odniesienia lub wskaźniki mogą być pobierane do automatycznie przydzielanych obiektów i używane polimorficznie; oglądać mnie .[typical OO code] use new a lot
: może w jakiejś starej książce, ale kogo to obchodzi? Wszelkie niejasne współczesne C ++ omijająnew
/ surowe wskaźniki tam, gdzie to możliwe - i nie jest w żaden sposób mniejszy OO, robiąc toJeszcze jeden punkt do wszystkich powyższych poprawnych odpowiedzi, zależy to od rodzaju wykonywanego programowania. Jądro rozwijające się na przykład w systemie Windows -> Stos jest poważnie ograniczony i może nie być możliwe przyjmowanie błędów stron, tak jak w trybie użytkownika.
W takich środowiskach nowe lub podobne do C wywołania API są preferowane, a nawet wymagane.
Oczywiście jest to jedynie wyjątek od reguły.
źródło
new
przydziela obiekty na stercie. W przeciwnym razie obiekty są przydzielane na stosie. Sprawdź różnicę między nimi .źródło