Używam C ++ przez krótki czas i zastanawiam się nad nowym słowem kluczowym. Po prostu powinienem go używać, czy nie?
1) Z nowym słowem kluczowym ...
MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
2) Bez nowego słowa kluczowego ...
MyClass myClass;
myClass.MyField = "Hello world!";
Z punktu widzenia implementacji nie wydają się tak różne (ale jestem pewien, że tak) ... Jednak moim podstawowym językiem jest C # i oczywiście pierwsza metoda jest tym, do czego jestem przyzwyczajony.
Wydaje się, że trudność polega na tym, że metoda 1 jest trudniejsza w użyciu z klasami std C ++.
Jakiej metody powinienem użyć?
Aktualizacja 1:
Ostatnio użyłem nowego słowa kluczowego dla pamięci sterty (lub darmowego magazynu ) dla dużej tablicy, która wychodziła poza zakres (tj. Była zwracana z funkcji). Tam, gdzie wcześniej korzystałem ze stosu, co spowodowało uszkodzenie połowy elementów poza zakresem, przełączenie na użycie sterty zapewniło, że elementy są taktowane. Tak!
Aktualizacja 2:
Mój przyjaciel niedawno powiedział mi, że istnieje prosta zasada używania new
słowa kluczowego; za każdym razem, gdy piszesz new
, pisz delete
.
Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.
Pomaga to uniknąć wycieków pamięci, ponieważ zawsze musisz gdzieś usunąć miejsce (tj. Kiedy wycinasz i wklejasz je do destruktora lub w inny sposób).
std::vector
istd::shared_ptr
. Zawierają one połączenia do ciebienew
idelete
dla ciebie, więc jeszcze mniej prawdopodobne jest wyciek pamięci. Zadaj sobie na przykład: czy zawsze pamiętasz o umieszczeniu odpowiedniegodelete
wszędzie tam, gdzie mógłby zostać zgłoszony wyjątek?delete
Ręczne wkładanie ws jest trudniejsze niż mogłoby się wydawać.Odpowiedzi:
Metoda 1 (przy użyciu
new
)delete
późniejszego jawnego określenia obiektu. (Jeśli go nie usuniesz, możesz spowodować wyciek pamięci)delete
jej przydzielenia . (tzn. możeszreturn
stworzyć obiekt za pomocąnew
)delete
d; i zawsze powinien zostać usunięty , niezależnie od tego, którą ścieżkę kontroli wybrano lub jeśli zgłoszono wyjątki.Metoda 2 (nieużywanie
new
)delete
później.return
wskazywać na obiekt na stosie)O ile użyć; wybierasz metodę, która najlepiej Ci odpowiada, biorąc pod uwagę powyższe ograniczenia.
Kilka łatwych przypadków:
delete
(i potencjalnym powodem wycieków pamięci ), nie powinieneś używaćnew
.new
źródło
Istnieje ważna różnica między nimi.
Wszystko, co nie jest przydzielone,
new
zachowuje się podobnie do typów wartości w języku C # (a ludzie często mówią, że te obiekty są przydzielane na stosie, co jest prawdopodobnie najczęstszym / oczywistym przypadkiem, ale nie zawsze jest prawdziwe. Dokładniej, obiekty przydzielone bez użycianew
mają automatyczne przechowywanie czas trwania Wszystko przydzielone za pomocąnew
jest przydzielane na stercie i zwracany jest do niego wskaźnik, dokładnie tak jak typy referencyjne w języku C #.Wszystko przydzielone na stosie musi mieć stały rozmiar, określony w czasie kompilacji (kompilator musi poprawnie ustawić wskaźnik stosu, lub jeśli obiekt należy do innej klasy, musi dostosować rozmiar tej innej klasy) . Dlatego tablice w języku C # są typami referencyjnymi. Muszą być, ponieważ w przypadku typów referencyjnych możemy w czasie wykonywania decydować o wymaganej ilości pamięci. To samo dotyczy tutaj. Tylko tablice o stałym rozmiarze (rozmiar, który można określić w czasie kompilacji) mogą być przydzielane z automatycznym czasem przechowywania (na stosie). Tablice o dynamicznych rozmiarach muszą być przydzielane na stercie przez wywołanie
new
.(I tu kończy się wszelkie podobieństwo do C #)
Teraz wszystko, co przydzielono na stosie, ma „automatyczny” czas przechowywania (można faktycznie zadeklarować zmienną jako
auto
, ale jest to ustawienie domyślne, jeśli nie określono innego typu pamięci, więc słowo kluczowe nie jest tak naprawdę używane w praktyce, ale właśnie tam pochodzi z)Automatyczny czas przechowywania oznacza dokładnie, jak to brzmi, czas trwania zmiennej jest obsługiwany automatycznie. Natomiast wszystko przydzielone na stercie musi zostać ręcznie usunięte przez Ciebie. Oto przykład:
Ta funkcja tworzy trzy wartości, które warto rozważyć:
W wierszu 1 deklaruje zmienną
b
typubar
na stosie (automatyczny czas trwania).W linii 2 deklaruje
bar
wskaźnikb2
na stosie (automatyczny czas trwania) i wywołuje new, przydzielającbar
obiekt na stercie. (dynamiczny czas trwania)Kiedy funkcja powróci, wydarzy się: Po pierwsze,
b2
wykracza poza zakres (kolejność niszczenia jest zawsze przeciwna do kolejności konstrukcji). Aleb2
to tylko wskazówka, więc nic się nie dzieje, pamięć, którą zajmuje, jest po prostu uwolniona. I co ważne, pamięć, na którą wskazuje (bar
instancja na stercie) NIE jest dotykana. Uwolniony jest tylko wskaźnik, ponieważ tylko wskaźnik miał automatyczny czas trwania. Po drugie,b
wychodzi poza zakres, więc ponieważ ma on automatyczny czas trwania, wywoływany jest jego destruktor, a pamięć jest zwalniana.A
bar
instancja na stercie? Prawdopodobnie wciąż tam jest. Nikt nie zadał sobie trudu, aby go usunąć, więc wyciekła pamięć.Z tego przykładu możemy zobaczyć, że wszystko, co ma automatyczny czas trwania, ma gwarancję, że zostanie wywołany jego destruktor, gdy wykroczy poza zakres. To się przydaje. Ale wszystko, co jest przydzielane na stosie, trwa tak długo, jak jest to potrzebne, i może być dynamicznie zmieniane, jak w przypadku tablic. To też jest przydatne. Możemy to wykorzystać do zarządzania przydziałami pamięci. Co jeśli klasa Foo przydzieli część pamięci na stercie w swoim konstruktorze i usunie tę pamięć w swoim destruktorze. Wtedy moglibyśmy uzyskać to, co najlepsze z obu światów, bezpieczne przydziały pamięci, które z pewnością zostaną uwolnione, ale bez ograniczeń zmuszania wszystkiego do umieszczenia na stosie.
Dokładnie tak działa większość kodu C ++. Spójrz na przykład na bibliotekę standardową
std::vector
. Jest to zwykle przydzielane na stosie, ale można je dynamicznie zmieniać i zmieniać rozmiar. I robi to poprzez wewnętrzne przydzielanie pamięci na stercie, jeśli to konieczne. Użytkownik klasy nigdy tego nie widzi, więc nie ma szansy na wyciek pamięci lub zapomnienie o wyczyszczeniu przydzielonych zasobów.Zasada ta nosi nazwę RAII (Resource Acquisition is Initialization) i można ją rozszerzyć na dowolny zasób, który należy nabyć i zwolnić. (gniazda sieciowe, pliki, połączenia z bazą danych, blokady synchronizacji). Wszystkie z nich można zdobyć w konstruktorze i uwolnić w destruktorze, więc masz gwarancję, że wszystkie zdobyte zasoby zostaną ponownie uwolnione.
Zasadniczo nigdy nie używaj new / delete bezpośrednio z kodu wysokiego poziomu. Zawsze zawiń go w klasę, która może zarządzać pamięcią za Ciebie i która zapewni, że zostanie ponownie uwolniona. (Tak, mogą istnieć wyjątki od tej reguły. W szczególności inteligentne wskaźniki wymagają
new
bezpośredniego wywołania i przekazania wskaźnika do jego konstruktora, który następnie przejmuje kontrolę i zapewniadelete
prawidłowe wywołanie. Ale to nadal bardzo ważna zasada )źródło
Prawie nigdy nie zależy to od twoich preferencji pisania, ale od kontekstu. Jeśli chcesz umieścić obiekt na kilku stosach lub jeśli jest zbyt ciężki dla stosu, przydziel go w darmowym sklepie. Ponadto, ponieważ przydzielasz obiekt, jesteś również odpowiedzialny za zwolnienie pamięci. Wyszukaj
delete
operatora.Aby zmniejszyć ciężar korzystania z zarządzania darmowymi sklepami, ludzie wymyślili takie rzeczy jak
auto_ptr
iunique_ptr
. Zdecydowanie polecam je przejrzeć. Mogą nawet pomóc w problemach z pisaniem ;-)źródło
Jeśli piszesz w C ++, prawdopodobnie piszesz dla wydajności. Korzystanie z nowego i darmowego sklepu jest znacznie wolniejsze niż korzystanie ze stosu (szczególnie przy użyciu wątków), więc używaj go tylko wtedy, gdy go potrzebujesz.
Jak powiedzieli inni, potrzebujesz nowego, gdy twój obiekt musi żyć poza zakresem funkcji lub obiektu, obiekt jest naprawdę duży lub gdy nie znasz rozmiaru tablicy w czasie kompilacji.
Staraj się też unikać usuwania. Zamiast tego zawiń swój nowy w inteligentny wskaźnik. Pozwól, aby połączenie inteligentnego wskaźnika zostało usunięte.
W niektórych przypadkach inteligentny wskaźnik nie jest inteligentny. Nigdy nie przechowuj std :: auto_ptr <> wewnątrz kontenera STL. Zbyt wcześnie usunie wskaźnik ze względu na operacje kopiowania wewnątrz kontenera. Innym przypadkiem jest posiadanie naprawdę dużego kontenera STL wskaźników do obiektów. boost :: shared_ptr <> będzie miał mnóstwo narzutu prędkości, ponieważ powoduje wzrost liczby referencji w górę iw dół. W takim przypadku lepszym sposobem jest umieszczenie kontenera STL w innym obiekcie i nadanie temu obiektowi destruktora, który wywoła funkcję usuwania na każdym wskaźniku w kontenerze.
źródło
Krótka odpowiedź brzmi: jeśli jesteś początkującym w C ++, nigdy nie powinieneś używać
new
anidelete
siebie.Zamiast tego powinieneś używać inteligentnych wskaźników, takich jak
std::unique_ptr
istd::make_unique
(lub rzadziejstd::shared_ptr
istd::make_shared
). W ten sposób nie musisz martwić się tak bardzo o wycieki pamięci. A nawet jeśli jesteś bardziej zaawansowany, najlepszą praktyką byłoby zazwyczaj umieszczenie niestandardowego sposobu korzystania z niegonew
i umieszczeniedelete
go w małej klasie (takiej jak niestandardowy inteligentny wskaźnik), która jest przeznaczona tylko do rozwiązywania problemów związanych z cyklem życia obiektu.Oczywiście za kulisami te inteligentne wskaźniki nadal wykonują dynamiczne przydzielanie i zwalnianie, więc użycie ich przez kod nadal wiązałoby się z dodatkowym obciążeniem środowiska wykonawczego. Inne odpowiedzi tutaj omawiały te problemy i jak podejmować decyzje projektowe, kiedy używać inteligentnych wskaźników, a nie tylko tworzyć obiekty na stosie lub włączać je jako bezpośrednie elementy obiektu, na tyle, że ich nie powtórzę. Ale moje streszczenie byłoby następujące: nie używaj inteligentnych wskaźników ani dynamicznej alokacji, dopóki coś cię do tego nie zmusi.
źródło
Bez
new
słowa kluczowego przechowujesz je na stosie połączeń . Przechowywanie zbyt dużych zmiennych na stosie spowoduje przepełnienie stosu .źródło
Prosta odpowiedź brzmi: tak - new () tworzy obiekt na stercie (z niefortunnym efektem ubocznym, że musisz zarządzać jego żywotnością (poprzez jawne wywołanie na nim delete), podczas gdy druga forma tworzy obiekt na stosie w bieżącym zakres i ten obiekt zostanie zniszczony, gdy wyjdzie poza zakres.
źródło
Jeśli twoja zmienna jest używana tylko w kontekście jednej funkcji, lepiej jest użyć zmiennej stosu, tj. Opcji 2. Jak powiedzieli inni, nie musisz zarządzać czasem życia zmiennych stosu - są one zbudowane i zniszczone automatycznie. Również przydzielanie / zwalnianie zmiennej na stercie jest powolne w porównaniu. Jeśli twoja funkcja jest wywoływana wystarczająco często, zobaczysz ogromną poprawę wydajności, jeśli użyjesz zmiennych stosu względem zmiennych stosu.
To powiedziawszy, istnieje kilka oczywistych przypadków, w których zmienne stosu są niewystarczające.
Jeśli zmienna stosu ma duży obszar pamięci, wówczas istnieje ryzyko przepełnienia stosu. Domyślnie rozmiar stosu każdego wątku wynosi 1 MB w systemie Windows. Jest mało prawdopodobne, że utworzysz zmienną stosu o wielkości 1 MB, ale musisz pamiętać, że wykorzystanie stosu jest kumulatywne. Jeśli twoja funkcja wywołuje funkcję, która wywołuje inną funkcję, która wywołuje inną funkcję, która ..., zmienne stosu we wszystkich tych funkcjach zajmują miejsce na tym samym stosie. Funkcje rekurencyjne mogą szybko napotkać ten problem, w zależności od głębokości rekurencji. Jeśli jest to problem, możesz zwiększyć rozmiar stosu (niezalecane) lub przydzielić zmienną na stercie za pomocą nowego operatora (zalecane).
Innym, bardziej prawdopodobnym warunkiem jest to, że twoja zmienna musi „żyć” poza zakresem twojej funkcji. W takim przypadku należy przypisać zmienną do sterty, aby można było do niej dotrzeć poza zakresem dowolnej funkcji.
źródło
Czy przekazujesz myClass z funkcji, czy spodziewasz się, że istnieje poza tą funkcją? Jak powiedzieli niektórzy inni, chodzi o zakres, gdy nie przydzielasz sterty. Po wyjściu z funkcji znika (ostatecznie). Jednym z klasycznych błędów popełnianych przez początkujących jest próba utworzenia lokalnego obiektu jakiejś klasy w funkcji i zwrócenia go bez przydzielania go na stercie. Pamiętam debugowanie tego rodzaju rzeczy w moich wcześniejszych czasach, gdy robiłem c ++.
źródło
Druga metoda tworzy instancję na stosie, wraz z takimi rzeczami, jak coś zadeklarowane
int
i lista parametrów, które są przekazywane do funkcji.Pierwsza metoda pozwala na umieszczenie wskaźnika na stosie, który ustawiłeś w miejscu w pamięci, w którym nowy
MyClass
został przydzielony na stosie - lub w sklepie darmowym.Pierwsza metoda wymaga również tego,
delete
co tworzysznew
, podczas gdy w drugiej metodzie klasa jest automatycznie niszczona i uwalniana, gdy wypadnie poza zakres (zwykle następny nawias zamykający).źródło
Krótka odpowiedź brzmi: tak, słowo kluczowe „new” jest niezwykle ważne, ponieważ kiedy go używasz, dane obiektowe są przechowywane na stosie, a nie na stosie, co jest najważniejsze!
źródło