Jak zainicjować pamięć nowym operatorem w C ++?

176

Dopiero zaczynam poznawać C ++ i chcę poznać kilka dobrych nawyków. Jeśli właśnie przydzieliłem tablicę typu intz newoperatorem, jak mogę zainicjować je wszystkie na 0 bez samodzielnego przeglądania ich wszystkich w pętli? Powinienem po prostu użyć memset? Czy jest na to sposób „C ++”?

dreamlax
źródło
19
Jeśli chcesz uzyskać dobry nawyk C ++, unikaj bezpośredniego używania tablic i zamiast tego używaj wektorów. Vector zainicjuje wszystkie elementy niezależnie od typu, a następnie nie musisz pamiętać o wywołaniu operatora delete [].
brianegge,
@brianegge: A jeśli muszę przekazać tablicę do zewnętrznej funkcji C, czy mogę po prostu podać jej wektor?
dreamlax
12
Możesz przejść &vector[0].
jamesdlin
Oczywiście, kiedy przekazujesz tablice do funkcji C, zazwyczaj musisz określić wskaźnik do pierwszego elementu, & vector [0], jak powiedział @jamesdlin, oraz rozmiar tablicy, dostarczony w tym przypadku przez vector.size ().
Trebor Rude,
Powiązane (pyta o typy inne niż tablice): stackoverflow.com/questions/7546620/ ...
Aconcagua

Odpowiedzi:

392

Jest to zaskakująco mało znana funkcja C ++ (o czym świadczy fakt, że nikt jeszcze tego nie podał jako odpowiedzi), ale w rzeczywistości ma specjalną składnię do inicjowania wartości w tablicy:

new int[10]();

Zauważ, że musisz użyć pustych nawiasów - nie możesz na przykład użyć (0)ani niczego innego (dlatego jest to przydatne tylko do inicjalizacji wartości).

Jest to wyraźnie dozwolone w ISO C ++ 03 5.3.4 [wyr.new] / 15, który mówi:

Nowe wyrażenie, które tworzy obiekt typu, Tinicjuje ten obiekt w następujący sposób:

...

  • Jeśli nowy inicjator ma postać (), element jest inicjalizowany wartością (8.5);

i nie ogranicza typów, dla których jest to dozwolone, podczas gdy (expression-list)formularz jest wyraźnie ograniczony przez dalsze reguły w tej samej sekcji, tak że nie zezwala na typy tablicowe.

Pavel Minaev
źródło
1
Chociaż zgadzam się, że jest to mało znane, nie mogę (całkowicie) zgodzić się, że jest to naprawdę bardzo zaskakujące - zostało dodane w C ++ 03, co większość ludzi prawie zignorowała (ponieważ była to jedna z niewielu nowych rzeczy dodał).
Jerry Coffin
2
@Jerry: Muszę przyznać, że jeszcze nie wiedziałem (prawdopodobnie dlatego, że kiedy zacząłem czytać standard, był to już C ++ 03). To powiedziawszy, niezwykłe jest to, że wszystkie implementacje, które znam, obsługują to (myślę, że to dlatego, że jest to tak trywialne do wdrożenia).
Pavel Minaev,
2
Tak, wdrożenie jest dość trywialne. O ile są nowe, cała „inicjalizacja wartości” była nowa w C ++ 03.
Jerry Coffin
34
W C ++ 11 można użyć jednolitego initializtion także: new int[10] {}. Możesz również podać wartości do zainicjowania:new int[10] {1,2,3}
bames53
Proszę nie mylić default-initialized z value-initialized: oba są jasno zdefiniowane w standardzie i są różnymi inicjalizacjami.
Deduplicator
25

Zakładając, że naprawdę chcesz mieć tablicę, a nie std :: vector, „sposób C ++” wyglądałby tak

#include <algorithm> 

int* array = new int[n]; // Assuming "n" is a pre-existing variable

std::fill_n(array, n, 0); 

Ale pamiętaj, że pod maską nadal jest to po prostu pętla, która przypisuje każdemu elementowi wartość 0 (naprawdę nie ma innego sposobu, aby to zrobić, z wyjątkiem specjalnej architektury z obsługą na poziomie sprzętu).

Tyler McHenry
źródło
Nie przeszkadza mi, że pętla jest zaimplementowana pod funkcją, chciałem tylko wiedzieć, czy sam musiałem zaimplementować taką pętlę. Dzięki za wskazówkę.
dreamlax
4
Możesz być zaskoczony. Byłem. Na moim STL (zarówno GCC, jak i Dinkumware), std :: copy faktycznie zamienia się w memcpy, jeśli wykryje, że jest wywoływany z wbudowanymi typami. Nie zdziwiłbym się, gdyby std :: fill_n używał memset.
Brian Neal,
2
Nie. Użyj „Inicjalizacji wartości”, aby ustawić wszystkich członków na 0.
Martin York,
24

Istnieje wiele metod przydzielania tablicy typu wewnętrznego i wszystkie z nich są poprawne, chociaż to, która z nich wybrać, zależy ...

Ręczna inicjalizacja wszystkich elementów w pętli

int* p = new int[10];
for (int i = 0; i < 10; i++)
{
    p[i] = 0;
}

Korzystanie std::memsetz funkcji z<cstring>

int* p = new int[10];
std::memset(p, 0, sizeof(int) * 10);

Korzystanie std::fill_nz algorytmu z<algorithm>

int* p = new int[10];
std::fill_n(p, 10, 0);

Korzystanie z std::vectorkontenera

std::vector<int> v(10); // elements zero'ed

Jeśli dostępny jest C ++ 0x , użyj funkcji listy inicjalizacyjnej

int a[] = { 1, 2, 3 }; // 3-element static size array
vector<int> v = { 1, 2, 3 }; // 3-element array but vector is resizeable in runtime
mloskot
źródło
1
powinien być wektor <int> Jeśli dodałeś p = new int [10] (), masz pełną listę.
karsten
@mloskot, w pierwszym przypadku, gdy zainicjowałeś tablicę używając "new", jak nastąpi przekazanie przez referencję? Gdybym użył int array[SIZE] ={1,2,3,4,5,6,7};notacji, mógłbym użyć void rotateArray(int (& input)[SIZE], unsigned int k);deklaracji funkcji, co by było, gdybyśmy użyli pierwszej konwencji? jakieś sugestie?
Anu
1
Obawiam się, że przykład z std::memsetjest błędny - przechodzisz 10, wydaje się, że oczekuje się liczby bajtów - patrz en.cppreference.com/w/cpp/string/byte/memset . (Myślę, że to ładnie pokazuje, dlaczego należy unikać takich niskopoziomowych konstrukcji, jeśli to możliwe.)
Suma,
@Suma Świetny chwyt! Naprawiony. Wydaje się, że to kandydat na błąd sprzed dekady :-) Tak, zgadzam się z twoim komentarzem.
mloskot
7

Jeśli alokowana pamięć jest klasą z konstruktorem, który robi coś pożytecznego, operator new wywoła ten konstruktor i pozostawi zainicjowany obiekt.

Ale jeśli przydzielasz POD lub coś, co nie ma konstruktora, który inicjuje stan obiektu, nie możesz przydzielić pamięci i zainicjować tej pamięci operatorem new w jednej operacji. Masz jednak kilka opcji:

1) Zamiast tego użyj zmiennej stosu. Możesz alokować i inicjalizować domyślne w jednym kroku, na przykład:

int vals[100] = {0};  // first element is a matter of style

2) użycie memset(). Zwróć uwagę, że jeśli przydzielany obiekt nie jest POD , ustawienie go jest złym pomysłem. Jednym konkretnym przykładem jest to, że jeśli ustawisz klasę, która ma funkcje wirtualne, rozwalisz vtable i pozostawisz obiekt w stanie bezużytecznym.

3) Wiele systemów operacyjnych ma wywołania, które robią to, co chcesz - alokują na stercie i inicjalizują dane w czymś. Przykładem systemu Windows byłobyVirtualAlloc()

4) Zazwyczaj jest to najlepsza opcja. Unikaj konieczności samodzielnego zarządzania pamięcią. Możesz użyć kontenerów STL, aby zrobić wszystko, co zrobiłbyś z pamięcią surową, w tym alokować i inicjować wszystko za jednym zamachem:

std::vector<int> myInts(100, 0);  // creates a vector of 100 ints, all set to zero
John Dibling
źródło
6

Tak jest:

std::vector<int> vec(SIZE, 0);

Użyj wektora zamiast dynamicznie przydzielanej tablicy. Korzyści obejmują brak konieczności zawracania sobie głowy jawnym usuwaniem tablicy (jest ona usuwana, gdy wektor wychodzi poza zakres), a także to, że pamięć jest automatycznie usuwana, nawet jeśli zostanie zgłoszony wyjątek.

Edycja: Aby uniknąć dalszych negatywnych głosów ze strony osób, które nie zawracają sobie głowy czytaniem poniższych komentarzy, powinienem wyjaśnić, że ta odpowiedź nie mówi, że wektor jest zawsze właściwą odpowiedzią. Ale z pewnością jest to bardziej C ++ sposób niż „ręczne” upewnienie się, że usunięto tablicę.

Teraz w C ++ 11 istnieje również std :: array, która modeluje tablicę o stałym rozmiarze (w porównaniu do wektora, który może rosnąć). Istnieje również std :: unique_ptr, który zarządza dynamicznie alokowaną tablicą (która może być połączona z inicjalizacją, zgodnie z odpowiedzią w innych odpowiedziach na to pytanie). Każdy z nich jest bardziej C ++ sposobem niż ręczna obsługa wskaźnika do tablicy, IMHO.

villintehaspam
źródło
11
to tak naprawdę nie odpowiada na zadane pytanie.
John Knoeller,
1
Czy powinienem zawsze używać std::vectorzamiast dynamicznie przydzielanych tablic? Jakie są zalety używania tablicy zamiast wektora i odwrotnie?
dreamlax
1
@John Knoller: OP zapytał o sposób C ++ na zrobienie tego, powiedziałbym, że wektor jest sposobem C ++ na zrobienie tego. Oczywiście masz rację, że mogą istnieć sytuacje, które nadal będą wymagały zwykłej tablicy, a nie znając sytuacji OP, może to być jedna. Chyba nie, ponieważ wydaje się prawdopodobne, że OP nie wie o wektorach.
villintehaspam
1
@villintehaspam: Chociaż to rozwiązanie nie odpowiada na moje pytanie, jest to droga, którą zamierzam podążać. Tyler McHenry odpowiada na moje pytanie bardziej bezpośrednio i powinien pomóc zwłaszcza osobom, które nie mogą - z jakiegokolwiek powodu - używać std::vector.
dreamlax
2
@villintehaspam: Nie, to nie jest sposób w C ++. Jest to sposób na zrobienie tego w Javie. Trzymanie się vectorwszędzie, niezależnie od kontekstu, nazywa się „pisaniem kodu Java w C ++”.
AnT
2

std::filljest w jedną stronę. Pobiera dwa iteratory i wartość do wypełnienia regionu. To, lub pętla for, byłoby (jak przypuszczam) bardziej C ++ sposobem.

Ustawienie tablicy pierwotnych typów całkowitych na 0 memsetjest w porządku, chociaż może unieść brwi. Weź również pod uwagę calloc, chociaż jest to trochę niewygodne w użyciu z C ++ ze względu na rzutowanie.

Ze swojej strony prawie zawsze używam pętli.

(Nie lubię zastanawiać się nad intencjami ludzi, ale to prawda, że std::vectorwszystkie rzeczy są równe, lepiej niż ich używać new[]).


źródło
1

zawsze możesz użyć memset:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));
Gregor Brandt
źródło
Rozumiem, że mogę użyć memset, ale nie byłem pewien, czy to jest sposób C ++ na podejście do problemu.
dreamlax
1
Tak naprawdę nie jest to „sposób C ++”, ale też nie są to tablice surowe.
Pete Kirkham,
1
@gbrandt: To znaczy, że nie działa zbyt dobrze ani w C, ani w C ++. Działa dla większości wartości typu to char lub unsigned char. Działa dla większości typów wartości 0 (przynajmniej w większości implementacji). W przeciwnym razie jest to generalnie bezużyteczne.
Jerry Coffin
1
10 * sizeof( *myArray )jest bardziej udokumentowany i odporny na zmiany niż 10 * sizeof( int ).
Kevin Reid,
1
W każdym razie OP ma surową tablicę, a memset jest najszybszym i najłatwiejszym sposobem wyzerowania tej tablicy.
Gregor Brandt
-1

Zwykle w przypadku dynamicznych list elementów używasz rozszerzenia std::vector.

Generalnie używam memset lub pętli do dynamicznej alokacji pamięci surowej, w zależności od tego, jak zmienna przewiduję ten obszar kodu w przyszłości.

Paul Nathan
źródło