Natknąłem się na quiz, który dotyczył deklaracji tablicowej o różnych rozmiarach. Pierwszą rzeczą, jaka przyszła mi do głowy, jest to, że będę musiał użyć dynamicznej alokacji z new
poleceniem, w następujący sposób:
while(T--) {
int N;
cin >> N;
int *array = new int[N];
// Do something with 'array'
delete[] array;
}
Widziałem jednak, że jedno z rozwiązań dopuszcza następujący przypadek:
while(T--) {
int N;
cin >> N;
int array[N];
// Do something with 'array'
}
Po krótkich badaniach przeczytałem, że g ++ pozwala na to, ale zastanawiało mnie, w jakich przypadkach konieczne jest użycie dynamicznego przydzielania? A może kompilator tłumaczy to jako alokację dynamiczną?
Funkcja usuwania jest włączona. Należy jednak pamiętać, że pytanie tutaj nie dotyczy wycieków pamięci.
c++
arrays
dynamic-memory-allocation
static-memory-allocation
learning_dude
źródło
źródło
std::vector
zamiast tego (std::vector<int> array(N);
).new OBJ
bezpośrednie wywoływanie .Odpowiedzi:
Żaden fragment, który pokazujesz, nie jest idiomatycznym, nowoczesnym kodem C ++.
new
orazdelete
(inew[]
idelete[]
) nie są przestarzałe w C ++ i nigdy nie będą. Są jeszcze sposób do wystąpienia dynamicznie alokowanych obiektów. Jednak, jak trzeba zawsze dopasowują z (oraz z ), są najlepiej utrzymane w klasach (biblioteka), które zapewniają to dla ciebie. Zobacz, dlaczego programiści C ++ powinni zminimalizować użycie „nowego”? .new
delete
new[]
delete[]
Twój pierwszy fragment używa „naga”,
new[]
a następnie nigdy niedelete[]
tworzy utworzonej tablicy. To jest problem.std::vector
robi wszystko, czego potrzebujesz tutaj w porządku. Będzie wykorzystywał jakąś formęnew
zakulisową (nie będę zagłębiał się w szczegóły implementacji), ale dla wszystkiego, co musisz się przejmować, jest to dynamiczna tablica, ale lepsza i bezpieczniejsza.Drugi fragment używa „tablic o zmiennej długości” (VLA), funkcji C, na którą zezwalają niektóre kompilatory w C ++ jako rozszerzenie. W przeciwieństwie do
new
VLA są zasadniczo alokowane na stosie (bardzo ograniczone zasoby). Co ważniejsze, nie są one standardową funkcją C ++ i należy ich unikać, ponieważ nie są przenośne. Z pewnością nie zastępują one alokacji dynamicznej (tj. Sterty).źródło
Qt
, ponieważ wszystkie jego klasy podstawowe mają śmieciarze, więc po prostu z niego korzystasznew
i o nim zapominasz. W przypadku elementów GUI, gdy widget nadrzędny jest zamknięty, dzieci wychodzą poza zakres i są automatycznie usuwane.new
nadal ma pasującedelete
; po prostudelete
s są wykonywane przez nadrzędny widget, a nie w tym samym bloku kodu conew
s.Cóż, na początek,
new
/delete
nie stają się przestarzałe.W twoim konkretnym przypadku nie są to jednak jedyne rozwiązania. To, co wybierzesz, zależy od tego, co ukryłeś pod komentarzem „zrób coś z tablicą”.
W drugim przykładzie użyto niestandardowego rozszerzenia VLA, które próbuje dopasować tablicę do stosu. Ma to pewne ograniczenia - mianowicie ograniczony rozmiar i niemożność użycia tej pamięci po tym, jak tablica wyłączy się z zakresu. Nie możesz go przenieść, „zniknie” po rozwinięciu stosu.
Więc jeśli Twoim jedynym celem jest wykonanie lokalnego obliczenia, a następnie wyrzucenie danych, może faktycznie działać poprawnie. Jednak bardziej niezawodnym podejściem byłoby dynamiczne przydzielanie pamięci, najlepiej przy pomocy
std::vector
. W ten sposób zyskujesz możliwość tworzenia miejsca na dokładnie tyle elementów, ile potrzebujesz, na podstawie wartości czasu wykonywania (do tego właśnie dążymy przez cały czas), ale również ładnie się oczyści i możesz go przenieść. tego zakresu, jeśli chcesz zachować pamięć na później.Krąży z powrotem do początku,
vector
będzie prawdopodobnie użyćnew
kilku warstw głębiej, ale nie powinny być związane z tym, jak to przedstawia interfejs jest o wiele lepsza. W tym sensie używanienew
idelete
można uznać za zniechęcające.źródło
new
idelete
, ale raczej używać inteligentnych wskaźników, takich jakstd::unique_pointer
.std::unique_ptr
std::unique_ptr
domyślne wywołania destruktoradelete
lubdelete[]
, co oznacza, że posiadany obiekt musi być przydzielony przeznew
lub wnew[]
każdym razie, w których wywołania były ukrytestd::make_unique
od C ++ 14.Drugi przykład wykorzystuje tablice o zmiennej długości (VLA), które w rzeczywistości są funkcją C99 ( nie C ++!), Ale mimo to są obsługiwane przez g ++ .
Zobacz także tę odpowiedź .
Zauważ, że tablice o zmiennej długości różnią się od
new
/delete
i nie „przestarzają” ich w żaden sposób.Należy również pamiętać, że VLA nie są ISO C ++.
źródło
Nowoczesne C ++ zapewnia łatwiejsze metody pracy z dynamicznymi alokacjami. Inteligentne wskaźniki mogą zająć się czyszczeniem po wyjątkach (które mogą się zdarzyć gdziekolwiek, jeśli jest to dozwolone) i wczesnych zwrotach, gdy tylko odnośne struktury danych wykroczą poza zakres, więc warto zastosować je zamiast tego:
Z C ++ 14 możesz także pisać
wygląda to jeszcze ładniej i zapobiegnie wyciekowi pamięci, jeśli alokacja się nie powiedzie. Od C ++ 20 powinieneś być w stanie zrobić tyle co
dla mnie to wciąż się nie kompiluje w momencie pisania w gcc 7.4.0. W tych dwóch przykładach używamy również
auto
zamiast deklaracji typu po lewej stronie. We wszystkich przypadkach używaj tablicy jak zwykle:Wycieki pamięci
new
i awarie od podwojonychdelete
jest czymś, co C ++ bazowało od wielu lat, będąc „centralnym punktem” argumentów za przejściem na inne języki. Może lepiej tego uniknąć.źródło
unique/shared_ptr
konstruktorów na korzyśćmake_unique/shared
, nie tylko nie musisz pisać skonstruowanego typu dwa razy (używającauto
), ale nie ryzykujesz wycieku pamięci lub zasobów, jeśli konstrukcja zawiedzie w części (jeśli używasz typu, który może zawieść)make_shared<int[]>
dużo, kiedy prawie zawsze chceszvector<int>
, ale dobrze wiedzieć.unique_ptr
konstruktor IIRC nie jest nim, więc dlaT
tego nie ma konstruktorów, więc nie ma ryzyka wyciekówunique_ptr(new int[size])
ishared_ptr
ma następujące cechy : „Jeśli zostanie zgłoszony wyjątek, usuwanie p jest wywoływane, gdy T nie jest typem tablicy, usuń [ ] p inaczej. ”, więc masz ten sam efekt - ryzykounique/shared_ptr(new MyPossiblyAllocatingType[size])
.nowe i usuwane nie są przestarzałe.
Obiekty utworzone przez nowego operatora można przekazać przez odniesienie. Obiekty można usunąć za pomocą delete.
nowe i usuń to podstawowe aspekty języka. Trwałością obiektu można zarządzać za pomocą nowych i usuń. Te z pewnością nie będą przestarzałe.
Instrukcja - int tablica [N] to sposób definiowania tablicy. Tablicy można używać w zakresie otaczającego bloku kodu. Nie można go przekazać tak, jak obiekt jest przekazywany do innej funkcji.
źródło
Pierwszy przykład wymaga
delete[]
na końcu, w przeciwnym razie nastąpi wyciek pamięci.Drugi przykład wykorzystuje zmienną długość tablicy, która nie jest obsługiwana przez C ++; nie tylko pozwala na ekspresję stałej długości tablicy .
W takim przypadku przydatne jest użycie
std::vector<>
jako rozwiązanie; który łączy wszystkie działania, które można wykonać na tablicy, w klasę szablonów.źródło
Składnia wygląda jak C ++, ale ten idiom jest podobny do zwykłego starego Algol60. Powszechnie było mieć takie bloki kodu:
Przykład można zapisać jako:
Czasami tęsknię za tym w obecnych językach;)
źródło