Wie initialisiere ich Speicher mit neuem Operator in C ++?

175

Ich fange gerade erst an, in C ++ einzusteigen, und ich möchte einige gute Gewohnheiten aufgreifen. Wenn ich intdem newOperator gerade ein Array vom Typ zugewiesen habe , wie kann ich sie alle auf 0 initialisieren, ohne sie alle selbst zu durchlaufen? Soll ich nur verwenden memset? Gibt es eine "C ++" - Möglichkeit, dies zu tun?

Dreamlax
quelle
19
Wenn Sie eine gute C ++ - Gewohnheit übernehmen möchten, vermeiden Sie die direkte Verwendung von Arrays und verwenden Sie stattdessen den Vektor. Vector initialisiert alle Elemente unabhängig vom Typ, und Sie müssen nicht daran denken, den Operator delete [] aufzurufen.
Brianegge
@brianegge: Was ist, wenn ich ein Array an eine externe C-Funktion übergeben muss, kann ich ihm einfach den Vektor geben?
Dreamlax
12
Sie können passieren &vector[0].
Jamesdlin
Wenn Sie Arrays an C-Funktionen übergeben, müssen Sie normalerweise den Zeiger auf das erste Element, & vector [0], wie @jamesdlin sagte, und die Größe des Arrays angeben, die in diesem Fall von vector.size () bereitgestellt wird.
Trebor Rude
Verwandte (fragt nach Nicht-Array-Typen): stackoverflow.com/questions/7546620/…
Aconcagua

Antworten:

392

Es ist eine überraschend wenig bekannte Funktion von C ++ (was durch die Tatsache belegt wird, dass dies noch niemand als Antwort gegeben hat), aber es hat tatsächlich eine spezielle Syntax für die Wertinitialisierung eines Arrays:

new int[10]();

Beachten Sie, dass Sie müssen die leeren Klammern verwenden - Sie können nicht, zum Beispiel, die Verwendung (0)oder irgendetwas anderes (weshalb diese für Wert Initialisierung nur dann sinnvoll ist).

Dies ist ausdrücklich in ISO C ++ 03 5.3.4 [expr.new] / 15 zulässig, in dem es heißt:

Ein neuer Ausdruck, der ein Objekt vom Typ erstellt, Tinitialisiert dieses Objekt wie folgt:

...

  • Wenn der Neuinitialisierer das Formular hat (), wird das Element wertinitialisiert (8.5).

und schränkt die Typen, für die dies zulässig ist, nicht ein, während das (expression-list)Formular durch weitere Regeln im selben Abschnitt explizit eingeschränkt wird, sodass keine Array-Typen zulässig sind .

Pavel Minaev
quelle
1
Obwohl ich zustimme, dass dies wenig bekannt ist, kann ich nicht (vollständig) zustimmen, dass es wirklich sehr überraschend ist - es wurde in C ++ 03 hinzugefügt, was die meisten Leute fast ignoriert zu haben scheinen (da dies eines der wenigen neuen Dinge war es hat hinzugefügt).
Jerry Coffin
2
@ Jerry: Ich muss zugeben, dass ich es noch nicht wusste (wahrscheinlich, weil es beim Lesen des Standards bereits C ++ 03 war). Trotzdem ist es bemerkenswert, dass alle mir bekannten Implementierungen dies unterstützen (ich denke, das liegt daran, dass die Implementierung so trivial ist).
Pavel Minaev
2
Ja, die Implementierung ist ziemlich trivial. Soweit neu, war alle "Wertinitialisierung" in C ++ 03 neu.
Jerry Coffin
34
In C ++ 11 können Sie auch eine einheitliche Initialisierung verwenden : new int[10] {}. Sie können auch Werte new int[10] {1,2,3}
angeben,
Bitte verwechseln Sie Standardinitialisierung nicht mit Wertinitialisierung: Beide sind im Standard klar definiert und unterschiedliche Initialisierungen.
Deduplikator
25

Angenommen, Sie möchten wirklich ein Array und keinen std :: vector, dann wäre dies der "C ++ - Weg"

#include <algorithm> 

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

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

Beachten Sie jedoch, dass dies unter der Haube eigentlich immer noch nur eine Schleife ist, die jedes Element 0 zuweist (es gibt wirklich keine andere Möglichkeit, dies zu tun, außer einer speziellen Architektur mit Unterstützung auf Hardwareebene).

Tyler McHenry
quelle
Es macht mir nichts aus, wenn die Schleife unter einer Funktion implementiert ist, ich wollte nur wissen, ob ich eine solche Schleife selbst implementieren musste oder nicht. Danke für den Tipp.
Dreamlax
4
Sie könnten überrascht sein. Ich war. In meiner STL (sowohl GCC als auch Dinkumware) wird std :: copy tatsächlich zu einem Memcpy, wenn festgestellt wird, dass es mit integrierten Typen aufgerufen wird. Es würde mich nicht wundern, wenn std :: fill_n memset verwendet.
Brian Neal
2
Nee. Verwenden Sie 'Wertinitialisierung', um alle Mitglieder auf 0 zu setzen.
Martin York
24

Es gibt eine Reihe von Methoden, um ein Array vom intrinsischen Typ zuzuweisen, und alle diese Methoden sind korrekt, obwohl die Auswahl ...

Manuelle Initialisierung aller Elemente in der Schleife

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

Verwenden der std::memsetFunktion von<cstring>

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

Verwenden des std::fill_nAlgorithmus von<algorithm>

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

Mit std::vectorContainern

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

Wenn C ++ 0x verfügbar, mit Initialisiererliste Funktionen

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
quelle
1
sollte vector <int> sein Wenn Sie p = new int [10] () hinzugefügt haben, hatten Sie eine vollständige Liste.
Karsten
@mloskot, im ersten Fall, in dem Sie ein Array mit "new" initialisiert haben, wie wird die Referenzübergabe erfolgen? Wenn ich die int array[SIZE] ={1,2,3,4,5,6,7};Notation verwenden void rotateArray(int (& input)[SIZE], unsigned int k);würde, könnte ich meine Funktionsdeklaration verwenden. Was wäre, wenn ich die erste Konvention verwenden würde? irgendein Vorschlag?
Anu
1
Ich fürchte, das Beispiel mit std::memsetist falsch - Sie übergeben 10, es scheint die Anzahl der Bytes zu erwarten - siehe en.cppreference.com/w/cpp/string/byte/memset . (Ich denke, dies zeigt sehr gut, warum man ein solches Konstrukt auf niedriger Ebene nach Möglichkeit vermeiden sollte.)
Suma
@ Suma Toller Fang! Fest. Dies scheint ein Kandidat für einen zehn Jahre alten Fehler zu sein :-) Ja, ich stimme Ihrem Kommentar zu.
Mloskot
7

Wenn der Speicher, den Sie zuweisen, eine Klasse mit einem Konstruktor ist, der etwas Nützliches tut, ruft der Operator new diesen Konstruktor auf und lässt Ihr Objekt initialisiert.

Wenn Sie jedoch einen POD oder etwas zuweisen, das keinen Konstruktor hat, der den Status des Objekts initialisiert, können Sie keinen Speicher zuweisen und diesen Speicher mit dem in einer Operation neuen Operator initialisieren. Sie haben jedoch mehrere Möglichkeiten:

1) Verwenden Sie stattdessen eine Stapelvariable. Sie können in einem Schritt wie folgt zuweisen und standardmäßig initialisieren :

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

2) verwenden memset(). Beachten Sie, dass wenn das Objekt, das Sie zuweisen, kein POD ist , es eine schlechte Idee ist, es zu speichern. Ein spezielles Beispiel ist, wenn Sie eine Klasse mit virtuellen Funktionen memseten, die vtable wegblasen und Ihr Objekt in einem unbrauchbaren Zustand belassen.

3) Viele Betriebssysteme haben Aufrufe, die das tun, was Sie wollen - auf einem Heap zuweisen und die Daten für etwas initialisieren. Ein Windows-Beispiel wäreVirtualAlloc()

4) Dies ist normalerweise die beste Option. Vermeiden Sie es, den Speicher selbst zu verwalten. Sie können STL-Container verwenden, um fast alles zu tun, was Sie mit Rohspeicher tun würden, einschließlich der Zuweisung und Initialisierung auf einen Schlag:

std::vector<int> myInts(100, 0);  // creates a vector of 100 ints, all set to zero
John Dibling
quelle
6

Ja da ist:

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

Verwenden Sie einen Vektor anstelle eines dynamisch zugewiesenen Arrays. Zu den Vorteilen gehört, dass Sie sich nicht darum kümmern müssen, das Array explizit zu löschen (es wird gelöscht, wenn der Vektor den Gültigkeitsbereich verlässt), und dass der Speicher automatisch gelöscht wird, selbst wenn eine Ausnahme ausgelöst wird.

Bearbeiten: Um weitere Drive-by-Abstimmungen von Personen zu vermeiden, die sich nicht die Mühe machen, die folgenden Kommentare zu lesen, sollte ich klarer machen, dass diese Antwort nicht besagt, dass der Vektor immer die richtige Antwort ist. Aber es ist sicher mehr C ++ als "manuell", um sicherzustellen, dass ein Array gelöscht wird.

Mit C ++ 11 gibt es jetzt auch std :: array, das ein Array mit konstanter Größe modelliert (gegenüber einem Vektor, der wachsen kann). Es gibt auch std :: unique_ptr, das ein dynamisch zugewiesenes Array verwaltet (das mit der Initialisierung kombiniert werden kann, wie in anderen Antworten auf diese Frage beantwortet). All dies ist eine C ++ - Methode, als den Zeiger auf das Array IMHO manuell zu handhaben.

villintehaspam
quelle
11
Dies beantwortet die gestellte Frage nicht wirklich.
John Knoeller
1
Sollte ich immer std::vectoranstelle von dynamisch zugewiesenen Arrays verwenden? Was sind die Vorteile der Verwendung eines Arrays gegenüber einem Vektor und umgekehrt?
Dreamlax
1
@ John Knoller: Das OP hat nach einer C ++ - Methode gefragt. Ich würde sagen, dass Vektor die C ++ - Methode ist, um dies zu tun. Natürlich haben Sie Recht, dass es Situationen geben kann, die immer noch ein einfaches Array erfordern und die Situation des OP nicht kennen. Ich würde allerdings nicht raten, da es plausibel erscheint, dass das OP nichts über Vektoren weiß.
Villintehaspam
1
@villintehaspam: Obwohl diese Lösung meine Frage nicht beantwortet, ist es der Weg, den ich einschlagen werde. Tyler McHenry beantwortet meine Frage direkter und sollte insbesondere für Menschen helfen, die - aus welchen Gründen auch immer - nicht verwenden können std::vector.
Dreamlax
2
@villintehaspam: Nein, das ist keine C ++ - Methode. Es ist eine Java-Methode. Das Festhalten vectorüberall unabhängig vom Kontext ist „Schreiben von Java - Code in C ++“ bezeichnet.
Am
2

std::fillist eine Möglichkeit. Benötigt zwei Iteratoren und einen Wert, mit dem die Region gefüllt wird. Das oder die for-Schleife wäre (ich nehme an) der C ++ - Weg.

Das Festlegen eines Arrays primitiver Ganzzahltypen auf 0 memsetist in Ordnung, kann jedoch die Augenbrauen hochziehen. Bedenken Sie auch calloc, obwohl die Verwendung von C ++ aufgrund der Besetzung etwas unpraktisch ist.

Ich für meinen Teil benutze so ziemlich immer eine Schleife.

(Ich mag es nicht, die Absichten der Leute zu hinterfragen, aber es ist wahr, dass std::vectoralle Dinge gleich sind und der Verwendung vorzuziehen sind new[].)


quelle
1

Sie können immer memset verwenden:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));
Gregor Brandt
quelle
Ich verstehe, dass ich verwenden kann memset, aber ich war mir nicht sicher, ob dies der C ++ - Weg war, um das Problem anzugehen.
Dreamlax
1
Es ist nicht wirklich der C ++ - Weg, aber es gibt auch keine rohen Arrays.
Pete Kirkham
1
@gbrandt: Das heißt, dass es weder in C noch in C ++ sehr gut funktioniert. Es funktioniert für die meisten Werte des Typs char oder unsigned char. Es funktioniert für die meisten Typen, deren Wert 0 ist (zumindest in den meisten Implementierungen). Ansonsten ist es im Allgemeinen nutzlos.
Jerry Coffin
1
10 * sizeof( *myArray )ist dokumentierter und veränderungssicherer als 10 * sizeof( int ).
Kevin Reid
1
In jedem Fall verfügt das OP über ein Raw-Array, und Memset ist der schnellste und einfachste Weg, dieses Array auf Null zu setzen.
Gregor Brandt
-1

In der Regel verwenden Sie für dynamische Listen von Elementen a std::vector.

Im Allgemeinen verwende ich ein Memset oder eine Schleife für die dynamische Zuweisung des Rohspeichers, je nachdem, wie variabel dieser Codebereich in Zukunft sein wird.

Paul Nathan
quelle