Czy „nowe” i „usuń” stają się przestarzałe w C ++?

68

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 newpoleceniem, 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.

learning_dude
źródło
54
Drugi przykład wykorzystuje tablicę o zmiennej długości, która nigdy nie była częścią C ++. W tym przypadku użyj std::vectorzamiast tego ( std::vector<int> array(N);).
Jakiś programista koleś
7
Bezpośrednia odpowiedź na twoje pytanie powinna brzmieć: nie, nie jest przestarzała. Chociaż współczesne wersje C ++ zapewniają wiele funkcji upraszczających zarządzanie własnością pamięci (inteligentne wskaźniki), nadal powszechną praktyką jest przydzielanie obiektów przez new OBJbezpośrednie wywoływanie .
pptaszni
8
Dla innych osób, które są zdezorientowane, dlaczego ludzie mówią o wyciekach pamięci, zredagowano pytanie, aby poprawić błąd, który nie był istotny dla pytania
Mike Caron
4
@Mannoj woli używać terminów Dynamiczny i Automatyczny do nakładania na stosy. Jest to rzadkie, ale możliwe jest wdrożenie C ++ bez stosów i stosów.
user4581301
1
Nic nie było przestarzałe w C ++ i nic nigdy nie będzie. To część tego, co oznacza C ++.
JoelFan

Odpowiedzi:

114

Żaden fragment, który pokazujesz, nie jest idiomatycznym, nowoczesnym kodem C ++.

neworaz delete(i new[]i delete[]) 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”? .newdeletenew[]delete[]

Twój pierwszy fragment używa „naga”, new[]a następnie nigdy nie delete[]tworzy utworzonej tablicy. To jest problem. std::vectorrobi wszystko, czego potrzebujesz tutaj w porządku. Będzie wykorzystywał jakąś formę newzakulisową (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 newVLA 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).

Max Langhof
źródło
3
Chciałbym dodać, że chociaż oficjalnie VLA nie są zawarte w standardzie, są one obsługiwane przez wszystkie główne kompilatory, a zatem decyzja, czy ich uniknąć, jest bardziej kwestią stylu / preferencji niż realistyczną troską o przenośność.
Stack Tracer
4
Pamiętaj też, że nie możesz zwrócić tablicy ani przechowywać jej w innym miejscu, aby VLA nigdy nie przeżyła czasu wykonania funkcji
Ruslan
16
@StackTracer Zgodnie z moją najlepszą wiedzą, MSVC nie obsługuje VLA. MSVC jest zdecydowanie „głównym kompilatorem”.
Max Langhof
2
„musisz zawsze dopasowywać nowy do usunięcia” - nie, jeśli pracujesz Qt, ponieważ wszystkie jego klasy podstawowe mają śmieciarze, więc po prostu z niego korzystasz newi o nim zapominasz. W przypadku elementów GUI, gdy widget nadrzędny jest zamknięty, dzieci wychodzą poza zakres i są automatycznie usuwane.
vsz
6
@vsz Nawet w Qt każdy newnadal ma pasujące delete; po prostu deletes są wykonywane przez nadrzędny widget, a nie w tym samym bloku kodu co news.
jjramsey
22

Cóż, na początek, new/ deletenie 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ć newkilku warstw głębiej, ale nie powinny być związane z tym, jak to przedstawia interfejs jest o wiele lepsza. W tym sensie używanie newi deletemożna uznać za zniechęcające.

Bartek Banachewicz
źródło
1
Zwróć uwagę na „... kilka warstw głębiej”. Jeśli chcesz wdrożyć własne kontenery, nadal powinieneś unikać używania newi delete, ale raczej używać inteligentnych wskaźników, takich jak std::unique_pointer.
Max
1
który tak naprawdę nazywa sięstd::unique_ptr
user253751
2
@Max: std::unique_ptrdomyślne wywołania destruktora deletelub delete[], co oznacza, że ​​posiadany obiekt musi być przydzielony przez newlub w new[]każdym razie, w których wywołania były ukryte std::make_uniqueod C ++ 14.
Laurent LA RIZZA
15

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/ deletei nie „przestarzają” ich w żaden sposób.

Należy również pamiętać, że VLA nie są ISO C ++.

andreee
źródło
13

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:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

Z C ++ 14 możesz także pisać

auto buffer_new = std::make_unique<int[]>(size);

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

auto a = std::make_shared<int[]>(size);

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ż autozamiast deklaracji typu po lewej stronie. We wszystkich przypadkach używaj tablicy jak zwykle:

buffer_old[0] = buffer_new[0] = 17;

Wycieki pamięci newi awarie od podwojonych deletejest 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ąć.

Audrius Meskauskas
źródło
Powinieneś unikać unique/shared_ptrkonstruktorów na korzyść make_unique/shared, nie tylko nie musisz pisać skonstruowanego typu dwa razy (używając auto), 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ść)
Simon Buchan
2
make_unique jest dostępny z tablicami z C ++ 14, a make_shared tylko z C ++ 20. Nadal rzadko jest to ustawienie domyślne, dlatego propozycja std :: make_shared <int []> (size) szukała mnie nieco wcześniej.
Audrius Meskauskas
Słusznie! Tak naprawdę nie używam make_shared<int[]>dużo, kiedy prawie zawsze chcesz vector<int>, ale dobrze wiedzieć.
Simon Buchan
Nadmiar pedanterii, ale unique_ptrkonstruktor IIRC nie jest nim, więc dla Ttego nie ma konstruktorów, więc nie ma ryzyka wycieków unique_ptr(new int[size])i shared_ptrma 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 - ryzyko unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan
3

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.

Gopinath
źródło
2

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.

brzytwa zigowa
źródło
3
Co masz na myśli mówiąc „do C ++ 11”? Jestem całkiem pewien, że VLA nigdy nie stały się częścią standardu.
churill
spójrz na standard c ++ 14 [standard c ++ 14] ( isocpp.org/files/papers/N3690.pdf ) na stronie 184 akapit 8.3.4
Zig Razor
4
To nie jest standard, ale tylko szkic i część o „tablicach związanych ze środowiskiem uruchomieniowym” nie dotarła do standardu, o ile mi wiadomo. Preferencja nie wspomina tam VLA.
churill
1
@zigrazor cppreference.com ma listę linków do najbliższych projektów przed / po publikacji każdego ze standardów. Opublikowane standardy nie są swobodnie dostępne, ale projekty te powinny być bardzo zbliżone. Jak widać z numerów dokumentów, połączony szkic jest starszą wersją roboczą dla C ++ 14.
orzech
2
@learning_dude To nie jest obsługiwane przez standardy. Odpowiedź jest (teraz) poprawna (choć krótka). Działa tylko dla Ciebie, ponieważ GCC pozwala na to jako niestandardowe rozszerzenie.
orzech
-4

Składnia wygląda jak C ++, ale ten idiom jest podobny do zwykłego starego Algol60. Powszechnie było mieć takie bloki kodu:

read n;
begin
    integer array x[1:n];
    ... 
end;

Przykład można zapisać jako:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Czasami tęsknię za tym w obecnych językach;)

kdo
źródło