Comment initialiser la mémoire avec un nouvel opérateur en C ++?

176

Je commence tout juste à entrer dans le C ++ et je veux prendre de bonnes habitudes. Si je viens d'allouer un tableau de type intavec l' newopérateur, comment puis-je les initialiser tous à 0 sans les parcourir tous moi-même? Dois-je simplement utiliser memset? Existe-t-il une manière «C ++» de le faire?

dreamlax
la source
19
Si vous voulez adopter une bonne habitude C ++, évitez d'utiliser directement des tableaux et utilisez plutôt vector. Vector initialisera tous les éléments quel que soit le type, et vous n'aurez plus besoin de vous rappeler d'appeler l'opérateur delete [].
brianegge
@brianegge: Que faire si j'ai besoin de passer un tableau à une fonction C externe, puis-je simplement lui donner le vecteur?
dreamlax
12
Vous pouvez passer &vector[0].
jamesdlin
Bien sûr, lorsque vous passez des tableaux à des fonctions C, vous devez généralement spécifier le pointeur vers le premier élément, & vector [0] comme @jamesdlin l'a dit, et la taille du tableau, fournie dans ce cas par vector.size ().
Trebor Rude
Connexes (demande des types non-array): stackoverflow.com/questions/7546620/…
Aconcagua

Réponses:

392

C'est une fonctionnalité étonnamment peu connue de C ++ (comme en témoigne le fait que personne n'a encore donné cette réponse), mais elle a en fait une syntaxe spéciale pour l'initialisation de la valeur d'un tableau:

new int[10]();

Notez que vous devez utiliser les parenthèses vides - vous ne pouvez pas, par exemple, utiliser (0)ou quoi que ce soit d'autre (c'est pourquoi cela n'est utile que pour l'initialisation de la valeur).

Ceci est explicitement autorisé par ISO C ++ 03 5.3.4 [expr.new] / 15, qui dit:

Une nouvelle expression qui crée un objet de type Tinitialise cet objet comme suit:

...

  • Si le nouvel initialiseur est de la forme (), l'élément est initialisé par la valeur (8.5);

et ne restreint pas les types pour lesquels cela est autorisé, alors que le (expression-list)formulaire est explicitement restreint par d'autres règles dans la même section de sorte qu'il n'autorise pas les types de tableau.

Pavel Minaev
la source
1
Bien que je convienne que cela soit peu connu, je ne peux pas (entièrement) convenir que c'est vraiment très surprenant - il a été ajouté en C ++ 03, que la plupart des gens semblent avoir presque ignoré (puisque c'était l'une des rares nouveautés) il a ajouté).
Jerry Coffin
2
@Jerry: Je dois admettre que je ne savais pas encore (probablement parce que quand j'ai commencé à lire le standard, c'était déjà C ++ 03). Cela dit, il est remarquable que toutes les implémentations que je connais prennent en charge cela (je suppose que c'est parce que c'est si simple à mettre en œuvre).
Pavel Minaev
2
Oui, c'est assez simple à mettre en œuvre. Pour autant que ce soit nouveau, toute «initialisation de valeur» était nouvelle dans C ++ 03.
Jerry Coffin
34
En C ++ 11 vous pouvez utiliser initializtion uniforme ainsi: new int[10] {}. Vous pouvez également fournir des valeurs pour initialiser avec:new int[10] {1,2,3}
bames53
Veuillez ne pas confondre initialisé par défaut avec initialisé par valeur: ils sont tous deux clairement définis dans la norme et sont des initialisations différentes.
Deduplicator
25

En supposant que vous vouliez vraiment un tableau et non un std :: vector, la "manière C ++" serait la suivante

#include <algorithm> 

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

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

Mais sachez que sous le capot, il ne s'agit en fait que d'une boucle qui attribue à chaque élément la valeur 0 (il n'y a vraiment pas d'autre moyen de le faire, à l'exception d'une architecture spéciale avec un support au niveau matériel).

Tyler McHenry
la source
Cela ne me dérange pas si la boucle est implémentée sous une fonction, je voulais juste savoir si je devais ou non implémenter une telle boucle moi-même. Merci pour le conseil.
dreamlax
4
Vous pourriez être surpris. J'étais. Sur mon STL (à la fois GCC et Dinkumware), std :: copy se transforme en fait en memcpy s'il détecte qu'il est appelé avec des types intégrés. Je ne serais pas surpris si std :: fill_n utilisait memset.
Brian Neal
2
Nan. Utilisez 'Value-Initialization' pour définir tous les membres sur 0.
Martin York
24

Il existe un certain nombre de méthodes pour allouer un tableau de type intrinsèque et toutes ces méthodes sont correctes, mais celle à choisir dépend ...

Initialisation manuelle de tous les éléments en boucle

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

Utilisation de la std::memsetfonction de<cstring>

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

Utilisation de l' std::fill_nalgorithme de<algorithm>

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

Utilisation du std::vectorconteneur

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

Si C ++ 0x est disponible, en utilisant les fonctionnalités de la liste d'initialisation

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
la source
1
devrait être vector <int> Si vous avez ajouté p = new int [10] (), vous aviez une liste complète.
karsten
@mloskot, dans le premier cas où vous avez initialisé un tableau en utilisant "new", comment se passera le passage par référence? Si j'ai utilisé la int array[SIZE] ={1,2,3,4,5,6,7};notation, je peux utiliser void rotateArray(int (& input)[SIZE], unsigned int k);serait ma déclaration de fonction, que serait lors de l'utilisation de la première convention? toute suggestion?
Anu
1
Je crains que l'exemple avec ne std::memsetsoit faux - vous passez 10, il semble s'attendre à un nombre d'octets - voir en.cppreference.com/w/cpp/string/byte/memset . (Je pense que cela montre bien pourquoi devrait-on éviter une telle construction de bas niveau lorsque cela est possible.)
Suma
@Suma Grande prise! Fixé. Cela semble être un candidat pour un bug vieux de dix ans :-) Oui, je suis d'accord avec votre commentaire.
mloskot
7

Si la mémoire que vous allouez est une classe avec un constructeur qui fait quelque chose d'utile, l'opérateur new appellera ce constructeur et laissera votre objet initialisé.

Mais si vous allouez un POD ou quelque chose qui n'a pas de constructeur qui initialise l'état de l'objet, vous ne pouvez pas allouer de mémoire et initialiser cette mémoire avec l'opérateur new en une seule opération. Cependant, vous avez plusieurs options:

1) Utilisez plutôt une variable de pile. Vous pouvez allouer et initialiser par défaut en une seule étape, comme ceci:

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

2) utilisation memset(). Notez que si l'objet que vous allouez n'est pas un POD , le paramétrer est une mauvaise idée. Un exemple spécifique est que si vous memset une classe qui a des fonctions virtuelles, vous allez faire sauter la vtable et laisser votre objet dans un état inutilisable.

3) De nombreux systèmes d'exploitation ont des appels qui font ce que vous voulez - allouer sur un tas et initialiser les données à quelque chose. Un exemple Windows seraitVirtualAlloc()

4) C'est généralement la meilleure option. Évitez du tout d'avoir à gérer la mémoire vous-même. Vous pouvez utiliser les conteneurs STL pour faire à peu près tout ce que vous feriez avec de la mémoire brute, y compris l'allocation et l'initialisation d'un seul coup:

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

Oui il y a:

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

Utilisez un vecteur au lieu d'un tableau alloué dynamiquement. Les avantages incluent ne pas avoir à se soucier de supprimer explicitement le tableau (il est supprimé lorsque le vecteur sort de la portée) et aussi que la mémoire est automatiquement supprimée même s'il y a une exception levée.

Edit: Pour éviter d'autres votes négatifs au volant de personnes qui ne prennent pas la peine de lire les commentaires ci-dessous, je dois préciser que cette réponse ne dit pas que le vecteur est toujours la bonne réponse. Mais c'est certainement une manière plus C ++ que "manuellement" de s'assurer de supprimer un tableau.

Désormais, avec C ++ 11, il existe également std :: array qui modélise un tableau de taille constante (vs un vecteur capable de croître). Il existe également std :: unique_ptr qui gère un tableau alloué dynamiquement (qui peut être combiné avec l'initialisation comme répondu dans d'autres réponses à cette question). N'importe lequel de ceux-ci est un moyen plus C ++ que la gestion manuelle du pointeur vers le tableau, à mon humble avis.

villintehaspam
la source
11
cela ne répond pas vraiment à la question qui a été posée.
John Knoeller
1
Dois-je toujours utiliser std::vectorau lieu de tableaux alloués dynamiquement? Quels sont les avantages de l'utilisation d'un tableau sur un vecteur, et vice versa?
dreamlax
1
@John Knoller: L'OP a posé des questions sur un moyen C ++ de le faire, je dirais que le vecteur est le moyen c ++ de le faire. Bien sûr, vous avez raison de dire qu'il pourrait y avoir des situations qui appelleraient encore un tableau simple et ne connaissant pas la situation de l'OP, cela pourrait en être une. Je suppose que non, car il semble plausible que l'OP ne connaisse pas les vecteurs.
villintehaspam
1
@villintehaspam: Bien que cette solution ne réponde pas à ma question, c'est le chemin que je vais emprunter. Tyler McHenry répond plus directement à ma question et devrait aider en particulier les personnes qui ne peuvent pas - pour une raison quelconque - utiliser std::vector.
dreamlax
2
@villintehaspam: Non, ce n'est pas une manière C ++ de le faire. C'est une manière Java de le faire. Rester vectorpartout quel que soit le contexte s'appelle «Ecrire du code Java en C ++».
Du
2

std::fillest un moyen. Prend deux itérateurs et une valeur pour remplir la région. Cela, ou la boucle for, serait (je suppose) la manière la plus C ++.

Pour définir un tableau de types entiers primitifs à 0 spécifiquement, memsetc'est bien, même si cela peut soulever des sourcils. Considérez également calloc, bien qu'il soit un peu gênant à utiliser à partir de C ++ en raison de la distribution.

Pour ma part, j'utilise presque toujours une boucle.

(Je n'aime pas remettre en question les intentions des gens, mais il est vrai que std::vector , toutes choses étant égales par ailleurs, préférable à l'utilisation new[].)


la source
1

vous pouvez toujours utiliser memset:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));
Gregor Brandt
la source
Je comprends que je peux utiliser memset, mais je n'étais pas sûr que c'était la manière C ++ d'aborder le problème.
dreamlax
1
Ce n'est pas vraiment la «méthode C ++», mais les tableaux bruts non plus.
Pete Kirkham
1
@gbrandt: Ce qui veut dire que cela ne fonctionne pas très bien en C ou en C ++. Cela fonctionne pour la plupart des valeurs du type est char ou unsigned char. Cela fonctionne pour la plupart des types de la valeur est 0 (au moins dans la plupart des implémentations). Sinon, c'est généralement inutile.
Jerry Coffin
1
10 * sizeof( *myArray )est plus documenté et à l'épreuve des changements que 10 * sizeof( int ).
Kevin Reid
1
Dans tous les cas, l'OP a un tableau brut et memset est le moyen le plus rapide et le plus simple de mettre à zéro ce tableau.
Gregor Brandt
-1

En règle générale, pour les listes d'éléments dynamiques, vous utilisez un fichier std::vector.

En général, j'utilise memset ou une boucle pour l'allocation dynamique de la mémoire brute, en fonction de la façon dont je prévois que cette zone de code sera à l'avenir.

Paul Nathan
la source