Namespaces
Variants

new expression

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
new expression
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Crée et initialise des objets avec une durée de stockage dynamique, c'est-à-dire des objets dont la durée de vie n'est pas nécessairement limitée par la portée dans laquelle ils ont été créés.

Table des matières

Syntaxe

:: (optionnel) new ( type  ) initialiseur-new  (optionnel) (1)
:: (optionnel) new type initialiseur-new  (optionnel) (2)
:: (optionnel) new ( arguments-placement  ) ( type  ) initialiseur-new  (optionnel) (3)
:: (optionnel) new ( arguments-placement  ) type initialiseur-new  (optionnel) (4)
1,2) Tente de créer un objet du type, désigné par le type-id type , qui peut être un type tableau , et peut inclure un spécificateur de type de substitution (depuis C++11) , ou inclure un nom de modèle de classe dont l'argument doit être déduit par déduction d'argument de modèle de classe (depuis C++17) .
3,4) Identique à (1,2) , mais fournit des arguments supplémentaires à la fonction d'allocation, voir placement new .

Explication

type - le type-id cible
new-initializer - une liste d'expressions entre parenthèses ou une liste d'initialisation entre accolades (depuis C++11)
placement-args - arguments de placement supplémentaires


L'expression new tente d'allouer de la mémoire puis essaie de construire et d'initialiser soit un objet unique anonyme, soit un tableau anonyme d'objets dans la mémoire allouée. L'expression new retourne un pointeur prvalue vers l'objet construit ou, si un tableau d'objets a été construit, un pointeur vers l'élément initial du tableau.

Syntaxe (1) ou (3) est requise si type inclut des parenthèses :

new int(*[10])();    // erreur : interprété comme (new int) (*[10]) ()
new (int (*[10])()); // correct : alloue un tableau de 10 pointeurs vers des fonctions

De plus, type est analysé de manière gourmande : il sera pris en incluant chaque jeton qui peut faire partie d'un déclarateur :

new int + 1; // correct : analysé comme (new int) + 1, incrémente un pointeur retourné par new int
new int * 1; // erreur : analysé comme (new int*) (1)

Le new-initializer n'est pas facultatif si

(depuis C++11)
  • un modèle de classe est utilisé dans le type dont les arguments doivent être déduits .
(depuis C++17)
double* p = new double[]{1, 2, 3}; // crée un tableau de type double[3]
auto p = new auto('c');            // crée un objet unique de type char. p est un char*
auto q = new std::integral auto(1);         // OK : q est un int*
auto q = new std::floating_point auto(true) // ERREUR : contrainte de type non satisfaite
auto r = new std::pair(1, true); // OK : r est un std::pair<int, bool>*
auto r = new std::vector;        // ERREUR : le type d'élément ne peut pas être déduit

Tableaux dynamiques

Si type est un type tableau, toutes les dimensions autres que la première doivent être spécifiées comme des expressions constantes entières (jusqu'en C++14) expressions constantes converties de type std::size_t (depuis C++14) positives, mais (uniquement lors de l'utilisation des syntaxes sans parenthèses (2) et (4) ) la première dimension peut être une expression de type entier, de type énumération, ou de type classe avec une unique fonction de conversion non explicite vers un type entier ou énumération (jusqu'en C++14) toute expression convertible en std::size_t (depuis C++14) . C'est la seule façon de créer directement un tableau avec une taille définie à l'exécution, ces tableaux sont souvent appelés tableaux dynamiques :

int n = 42;
double a[n][5]; // erreur
auto p1 = new  double[n][5];  // OK
auto p2 = new  double[5][n];  // erreur : seule la première dimension peut être non constante
auto p3 = new (double[n][5]); // erreur : la syntaxe (1) ne peut pas être utilisée pour les tableaux dynamiques

Le comportement est indéfini si la valeur dans la première dimension (convertie en type intégral ou énumération si nécessaire) est négative.

(jusqu'à C++11)

Dans les cas suivants, la valeur de l'expression spécifiant la première dimension est invalide :

  • l'expression est de type non-classe et sa valeur avant conversion en std::size_t est négative ;
  • l'expression est de type classe et sa valeur après la fonction de conversion définie par l'utilisateur et avant la seconde conversion standard est négative ;
  • la valeur de l'expression est supérieure à une limite définie par l'implémentation ;
  • la valeur est inférieure au nombre d'éléments du tableau fournis dans la liste d'initialisation entre accolades (incluant le ' \0 ' terminal sur un littéral de chaîne ).

Si la valeur dans la première dimension est invalide pour l'une de ces raisons,

(depuis C++11)

La première dimension de zéro est acceptable, et la fonction d'allocation est appelée.

Si new-initializer est une liste d'initialisation entre accolades, et que la première dimension est potentiellement évaluée et n'est pas une expression constante de base , les contraintes sémantiques de l'initialisation par copie d'un élément hypothétique du tableau à partir d'une liste d'initialisation vide sont vérifiées.

(depuis C++11)

Allocation

L'expression new alloue de la mémoire en appelant la fonction d'allocation appropriée. Si type est un type non-tableau, le nom de la fonction est operator new . Si type est un type tableau, le nom de la fonction est operator new [ ] .

Comme décrit dans la fonction d'allocation , le programme C++ peut fournir des remplacements globaux et spécifiques à la classe pour ces fonctions. Si l'expression new commence avec l'opérateur optionnel :: , comme dans :: new T ou :: new T [ n ] , les remplacements spécifiques à la classe seront ignorés (la fonction est recherchée dans la portée globale). Sinon, si T est un type classe, la recherche commence dans la portée de classe de T .

Lors de l'appel de la fonction d'allocation, l'expression new transmet le nombre d'octets demandé comme premier argument, de type std::size_t , qui correspond exactement à sizeof ( T ) pour un type T non-tableau.

L'allocation de tableau peut fournir une surcharge non spécifiée, qui peut varier d'un appel à new au suivant, sauf si la fonction d'allocation sélectionnée est la forme standard non-allouante. Le pointeur retourné par l'expression new sera décalé de cette valeur par rapport au pointeur retourné par la fonction d'allocation. De nombreuses implémentations utilisent la surcharge de tableau pour stocker le nombre d'objets dans le tableau, qui est utilisé par l'expression delete [ ] pour appeler le nombre correct de destructeurs. De plus, si l'expression new est utilisée pour allouer un tableau de char , unsigned char , ou std::byte (depuis C++17) , elle peut demander de la mémoire supplémentaire à la fonction d'allocation si nécessaire pour garantir un alignement correct des objets de tous les types dont la taille n'excède pas celle du tableau demandé, si un tel objet est ultérieurement placé dans le tableau alloué.

new expressions sont autorisées à élider ou combiner les allocations effectuées via des fonctions d'allocation remplaçables. En cas d'élision, le stockage peut être fourni par le compilateur sans appeler une fonction d'allocation (cela permet également d'optimiser les expressions new non utilisées). En cas de combinaison, l'allocation effectuée par une expression new E1 peut être étendue pour fournir un stockage supplémentaire à une autre expression new E2 si toutes les conditions suivantes sont remplies :

1) La durée de vie de l'objet alloué par E1 contient strictement la durée de vie de l'objet alloué par E2 .
2) E1 et E2 invoqueraient la même fonction d'allocation globale remplaçable.
3) Pour une fonction d'allocation levante, les exceptions dans E1 et E2 seraient d'abord attrapées dans le même gestionnaire.

Notez que cette optimisation n'est permise que lorsque des expressions new sont utilisées, et non par d'autres méthodes pour appeler une fonction d'allocation remplaçable : delete [ ] new int [ 10 ] ; peut être optimisé, mais operator delete ( operator new ( 10 ) ) ; ne peut pas l'être.

(depuis C++14)

Lors de l'évaluation d'une expression constante , un appel à une fonction d'allocation est toujours omis. Seules les expressions new qui résulteraient autrement en un appel à une fonction d'allocation globale remplaçable peuvent être évaluées dans des expressions constantes.

(depuis C++20)

Placement new

Si des placement-args sont fournis, ils sont passés à la fonction d'allocation comme arguments supplémentaires. De telles fonctions d'allocation sont appelées « placement new », d'après la fonction d'allocation standard void * operator new ( std:: size_t , void * ) , qui retourne simplement son second argument inchangé. Ceci est utilisé pour construire des objets dans un stockage alloué :

// dans n'importe quel bloc de portée...
{
    // Allouer statiquement le stockage avec une durée de stockage automatique
    // qui est suffisamment grand pour n'importe quel objet de type « T ».
    alignas(T) unsigned char buf[sizeof(T)];
    T* tptr = new(buf) T; // Construire un objet « T », en le plaçant directement dans votre
                          // stockage pré-alloué à l'adresse mémoire « buf ».
    tptr->~T();           // Vous devez appeler **manuellement** le destructeur de l'objet
                          // si ses effets secondaires sont nécessaires au programme.
}                         // Quitter ce bloc de portée désalloue automatiquement « buf ».

Note : cette fonctionnalité est encapsulée par les fonctions membres des Allocator classes.

Lors de l'allocation d'un objet dont l'exigence d'alignement dépasse __STDCPP_DEFAULT_NEW_ALIGNMENT__ ou d'un tableau de tels objets, l'expression new passe l'exigence d'alignement (encapsulée dans std::align_val_t ) comme second argument de la fonction d'allocation (pour les formes de placement, placement-arg apparaît après l'alignement, comme troisième, quatrième, etc. arguments). Si la résolution de surcharge échoue (ce qui se produit lorsqu'une fonction d'allocation spécifique à la classe est définie avec une signature différente, car elle masque les fonctions globales), la résolution de surcharge est tentée une seconde fois, sans l'alignement dans la liste d'arguments. Cela permet aux fonctions d'allocation spécifiques à la classe non conscientes de l'alignement de prendre la priorité sur les fonctions d'allocation globales conscientes de l'alignement.

(depuis C++17)
new T;      // appelle operator new(sizeof(T))
            // (C++17) ou operator new(sizeof(T), std::align_val_t(alignof(T))))
new T[5];   // appelle operator new[](sizeof(T)*5 + overhead)
            // (C++17) ou operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T))))
new(2,f) T; // appelle operator new(sizeof(T), 2, f)
            // (C++17) ou operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)

Si une fonction d'allocation non levante (par exemple, celle sélectionnée par new ( std:: nothrow ) T ) renvoie un pointeur nul en raison d'un échec d'allocation, alors l'expression new retourne immédiatement, elle ne tente pas d'initialiser un objet ou d'appeler une fonction de désallocation. Si un pointeur nul est passé comme argument à une expression de placement non allouante new , ce qui amène la fonction d'allocation de placement standard non allouante sélectionnée à renvoyer un pointeur nul, le comportement est indéfini.

Initialisation

L'objet créé par une expression new est initialisé selon les règles suivantes.

Si type n'est pas un type tableau, l'objet unique est construit dans la zone mémoire acquise :

(depuis C++11)

Si type est un type tableau, un tableau d'objets est initialisé :

  • Même si la première dimension est zéro, les contraintes sémantiques de l'initialisation par défaut d'un élément hypothétique doivent toujours être satisfaites.
  • Même si la première dimension est zéro, les contraintes sémantiques de l'initialisation à la valeur d'un élément hypothétique doivent toujours être satisfaites.
(depuis C++11)
  • Si new-initializer est une liste d'expressions non vide entre parenthèses, le tableau est aggregate-initialized .
(depuis C++20)

Échec d'initialisation

Si l'initialisation se termine en lançant une exception (par exemple depuis le constructeur), le programme recherche une fonction de désallocation correspondante, puis :

  • Si une fonction de désallocation appropriée peut être trouvée, la fonction de désallocation est appelée pour libérer la mémoire dans laquelle l'objet était en cours de construction. Après cela, l'exception continue de se propager dans le contexte de l'expression new .
  • Si aucune fonction de désallocation correspondante non ambiguë ne peut être trouvée, la propagation de l'exception n'entraîne pas la libération de la mémoire de l'objet. Cela n'est approprié que si la fonction d'allocation appelée n'alloue pas de mémoire, sinon cela risque de provoquer une fuite de mémoire.

La portée de la recherche de la fonction de désallocation correspondante est déterminée comme suit :

  • Si l'expression new ne commence pas par :: , et que le type alloué est soit un type classe T soit un tableau de type classe T , une recherche du nom de la fonction de désallocation est effectuée dans la portée de classe de T .
  • Sinon, ou si rien n'est trouvé dans la portée de classe de T , le nom de la fonction de désallocation est recherché en le cherchant dans la portée globale .

Pour une fonction d'allocation non-placement, la recherche normale de fonction de désallocation est utilisée pour trouver la fonction de désallocation correspondante (voir delete-expression ).

Pour une fonction d'allocation de placement, la fonction de désallocation correspondante doit avoir le même nombre de paramètres, et chaque type de paramètre à l'exception du premier est identique au type de paramètre correspondant de la fonction d'allocation (après transformations de paramètres ).

  • Si la recherche trouve une seule fonction de désallocation correspondante, cette fonction sera appelée ; sinon, aucune fonction de désallocation ne sera appelée.
  • Si la recherche trouve une fonction de désallocation non-placement et que cette fonction, considérée comme une fonction de désallocation placement, aurait été sélectionnée comme correspondance pour la fonction d'allocation, le programme est mal formé.

Dans tous les cas, la fonction de désallocation correspondante (si elle existe) doit être non supprimée et (depuis C++11) accessible depuis le point où l'expression new apparaît.

struct S
{
    // Fonction d'allocation avec placement :
    static void* operator new(std::size_t, std::size_t);
    // Fonction de désallocation sans placement :
    static void operator delete(void*, std::size_t);
};
S* p = new (0) S; // erreur : la fonction de désallocation sans placement correspond
                  //        à la fonction d'allocation avec placement

Si une fonction de désallocation est appelée dans une new expression (en raison d'un échec d'initialisation), les arguments passés à cette fonction sont déterminés comme suit :

  • Le premier argument est la valeur (de type void * ) retournée par l'appel de la fonction d'allocation.
  • Les autres arguments (uniquement pour les fonctions de désallocation de placement) sont les placement-args passés à la fonction d'allocation de placement.

Si l'implémentation est autorisée à introduire un objet temporaire ou à effectuer une copie de tout argument dans le cadre de l'appel à la fonction d'allocation, il n'est pas spécifié si le même objet est utilisé dans l'appel à la fois aux fonctions d'allocation et de désallocation.

Fuites de mémoire

Les objets créés par les expressions new (objets avec durée de stockage dynamique) persistent jusqu'à ce que le pointeur retourné par l'expression new soit utilisé dans une expression delete correspondante. Si la valeur originale du pointeur est perdue, l'objet devient inaccessible et ne peut être désalloué : une fuite de mémoire se produit.

Cela peut se produire si le pointeur est assigné à :

int* p = new int(7); // int alloué dynamiquement avec la valeur 7
p = nullptr; // fuite de mémoire

ou si le pointeur sort de la portée :

void f()
{
    int* p = new int(7);
} // fuite de mémoire

ou en raison d'une exception :

void f()
{
    int* p = new int(7);
    g();      // peut lever une exception
    delete p; // correct si aucune exception
} // fuite mémoire si g() lève une exception

Pour simplifier la gestion des objets alloués dynamiquement, le résultat d'une expression new est souvent stocké dans un pointeur intelligent : std::auto_ptr (jusqu'à C++17) std::unique_ptr , ou std::shared_ptr (depuis C++11) . Ces pointeurs garantissent que l'expression delete est exécutée dans les situations décrites ci-dessus.

Notes

Itanium C++ ABI exige que la surcharge d'allocation de tableau soit nulle si le type d'élément du tableau créé est trivialement destructible. MSVC fait de même.

Certaines implémentations (par exemple MSVC avant VS 2019 v16.7) nécessitent une surcharge d'allocation de tableau non nulle pour le placement de tableau non-allouant new si le type d'élément n'est pas trivialement destructible, ce qui n'est plus conforme depuis CWG issue 2382 .

Une expression de placement de tableau new non-allouante qui crée un tableau de unsigned char , ou de std::byte (depuis C++17) peut être utilisée pour créer implicitement des objets dans une région de stockage donnée : elle met fin à la durée de vie des objets chevauchant le tableau, puis crée implicitement des objets de types à durée de vie implicite dans le tableau.

std::vector offre des fonctionnalités similaires pour les tableaux dynamiques unidimensionnels.

Mots-clés

new

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement tel que publié Comportement correct
CWG 74 C++98 la valeur dans la première dimension doit avoir un type intégral les types énumération sont autorisés
CWG 299 C++98 la valeur dans la première dimension doit
avoir un type intégral ou énumération
les types classe avec une seule
fonction de conversion vers un type
intégral ou énumération sont autorisés
CWG 624 C++98 le comportement n'était pas spécifié lorsque
la taille de l'objet alloué dépasserait
la limite définie par l'implémentation
aucun stockage n'est obtenu et une
exception est levée dans ce cas
CWG 1748 C++98 le placement non-allouant new devait
vérifier si l'argument est nul
comportement indéfini pour un argument nul
CWG 1992 C++11 new ( std:: nothrow ) int [ N ]
pouvait lever std::bad_array_new_length
modifié pour retourner un pointeur nul
CWG 2102 C++98 il n'était pas clair si l'initialisation par défaut/par valeur
devait être bien formée lors de l'initialisation de tableaux vides
requis
CWG 2382 C++98 le placement non-allouant de tableau new
pouvait nécessiter une surcharge d'allocation
une telle surcharge d'allocation est interdite
CWG 2392 C++11 le programme pouvait être mal formé même si la
première dimension n'est pas potentiellement évaluée
bien formé dans ce cas
P1009R2 C++11 la limite du tableau ne pouvait pas être
déduite dans une expression new
déduction autorisée

Voir aussi