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

Crea e inicializa objetos con storage duration dinámico, es decir, objetos cuyo tiempo de vida no está necesariamente limitado por el ámbito en el que fueron creados.

Contenidos

Sintaxis

:: (opcional) new ( type  ) new-initializer  (opcional) (1)
:: (opcional) new type new-initializer  (opcional) (2)
:: (opcional) new ( placement-args  ) ( type  ) new-initializer  (opcional) (3)
:: (opcional) new ( placement-args  ) type new-initializer  (opcional) (4)
1,2) Intenta crear un objeto del tipo, denotado por el type-id type , que puede ser un tipo de arreglo , y puede incluir un especificador de tipo de marcador de posición (desde C++11) , o incluir un nombre de plantilla de clase cuyos argumentos deben deducirse mediante deducción de argumentos de plantilla de clase (desde C++17) .
3,4) Lo mismo que (1,2) , pero proporciona argumentos adicionales a la función de asignación, consulte placement new .

Explicación

type - el type-id objetivo
new-initializer - una lista de expresiones entre paréntesis o una lista de inicialización entre llaves (desde C++11)
placement-args - argumentos de colocación adicionales


La expresión new intenta asignar almacenamiento y luego intenta construir e inicializar un único objeto sin nombre, o un arreglo sin nombre de objetos en el almacenamiento asignado. La expresión new devuelve un puntero prvalue al objeto construido o, si se construyó un arreglo de objetos, un puntero al elemento inicial del arreglo.

Sintaxis (1) o (3) es requerida si type incluye paréntesis:

new int(*[10])();    // error: se interpreta como (new int) (*[10]) ()
new (int (*[10])()); // correcto: asigna un array de 10 punteros a funciones

Además, type se analiza de forma ambiciosa: se tomará para incluir cada token que pueda formar parte de un declarador:

new int + 1; // correcto: interpretado como (new int) + 1, incrementa un puntero devuelto por new int
new int * 1; // error: interpretado como (new int*) (1)

El new-initializer no es opcional si

(desde C++11)
  • se utiliza una plantilla de clase en type cuyos argumentos necesitan ser deducidos .
(desde C++17)
double* p = new double[]{1, 2, 3}; // crea un array de tipo double[3]
auto p = new auto('c');            // crea un único objeto de tipo char. p es un char*
auto q = new std::integral auto(1);         // OK: q es un int*
auto q = new std::floating_point auto(true) // ERROR: restricción de tipo no satisfecha
auto r = new std::pair(1, true); // OK: r es un std::pair<int, bool>*
auto r = new std::vector;        // ERROR: el tipo de elemento no puede deducirse

Arreglos dinámicos

Si type es un tipo array, todas las dimensiones excepto la primera deben especificarse como expresiones constantes integrales (hasta C++14) expresiones constantes convertidas de tipo std::size_t (desde C++14) positivas, pero (solo cuando se usan sintaxis sin paréntesis (2) y (4) ) la primera dimensión puede ser una expresión de tipo integral, tipo enumeración, o tipo clase con una única función de conversión no explícita a tipo integral o enumeración (hasta C++14) cualquier expresión convertible a std::size_t (desde C++14) . Esta es la única forma de crear directamente un array con tamaño definido en tiempo de ejecución, tales arrays se denominan frecuentemente arrays dinámicos :

int n = 42;
double a[n][5]; // error
auto p1 = new  double[n][5];  // OK
auto p2 = new  double[5][n];  // error: solo la primera dimensión puede ser no constante
auto p3 = new (double[n][5]); // error: la sintaxis (1) no puede usarse para arrays dinámicos

El comportamiento es indefinido si el valor en la primera dimensión (convertido a tipo integral o de enumeración si es necesario) es negativo.

(until C++11)

En los siguientes casos el valor de la expresión que especifica la primera dimensión es inválido:

  • la expresión es de tipo no-clase y su valor antes de la conversión a std::size_t es negativo;
  • la expresión es de tipo clase y su valor después de la función de conversión definida por el usuario y antes de la segunda conversión estándar es negativo;
  • el valor de la expresión es mayor que algún límite definido por la implementación;
  • el valor es menor que el número de elementos del array proporcionados en la lista de inicialización entre llaves (incluyendo el ' \0 ' terminal en un literal de cadena ).

Si el valor en la primera dimensión es inválido por cualquiera de estas razones,

(since C++11)

La primera dimensión de cero es aceptable, y se llama a la función de asignación.

Si new-initializer es una lista de inicialización entre llaves, y la primera dimensión es potencialmente evaluada y no es una expresión constante central , se verifican las restricciones semánticas de inicialización por copia de un elemento hipotético del array desde una lista de inicialización vacía.

(desde C++11)

Asignación

La expresión new asigna almacenamiento llamando a la función de asignación apropiada. allocation function . Si type es un tipo no-array, el nombre de la función es operator new . Si type es un tipo array, el nombre de la función es operator new [ ] .

Como se describe en la función de asignación , el programa C++ puede proporcionar reemplazos globales y específicos de clase para estas funciones. Si la expresión new comienza con el operador opcional :: , como en :: new T o :: new T [ n ] , se ignorarán los reemplazos específicos de clase (la función se busca en el ámbito global). De lo contrario, si T es un tipo de clase, la búsqueda comienza en el ámbito de clase de T .

Al llamar a la función de asignación, la expresión new pasa el número de bytes solicitados como primer argumento, de tipo std::size_t , que es exactamente sizeof ( T ) para T que no son arreglos.

La asignación de arreglos puede proporcionar una sobrecarga no especificada, que puede variar de una llamada a new a la siguiente, a menos que la función de asignación seleccionada sea la forma estándar no asignadora. El puntero devuelto por la expresión new estará desplazado por ese valor respecto al puntero devuelto por la función de asignación. Muchas implementaciones utilizan la sobrecarga del arreglo para almacenar el número de objetos en el arreglo, que es utilizado por la expresión delete [ ] para llamar al número correcto de destructores. Además, si la expresión new se utiliza para asignar un arreglo de char , unsigned char , o std::byte (desde C++17) , puede solicitar memoria adicional de la función de asignación si es necesario para garantizar la alineación correcta de objetos de todos los tipos no mayores que el tamaño del arreglo solicitado, si uno se coloca posteriormente en el arreglo asignado.

new expressions pueden omitir o combinar asignaciones realizadas mediante funciones de asignación reemplazables. En caso de omisión, el almacenamiento puede ser proporcionado por el compilador sin realizar la llamada a una función de asignación (esto también permite optimizar las expresiones new no utilizadas). En caso de combinación, la asignación realizada por una expresión new E1 puede extenderse para proporcionar almacenamiento adicional para otra expresión new E2 si se cumple todo lo siguiente:

1) El tiempo de vida del objeto asignado por E1 contiene estrictamente el tiempo de vida del objeto asignado por E2 .
2) E1 y E2 invocarían la misma función de asignación global reemplazable.
3) Para una función de asignación que lanza excepciones, las excepciones en E1 y E2 serían capturadas primero en el mismo manejador.

Nótese que esta optimización solo está permitida cuando se utilizan expresiones new , no cualquier otro método para llamar a una función de asignación reemplazable: delete [ ] new int [ 10 ] ; puede ser optimizado, pero operator delete ( operator new ( 10 ) ) ; no puede.

(since C++14)

Durante la evaluación de una expresión constante , una llamada a una función de asignación siempre se omite. Solo las expresiones new que de otro modo resultarían en una llamada a una función de asignación global reemplazable pueden evaluarse en expresiones constantes.

(since C++20)

Placement new

Si se proporcionan placement-args , se pasan a la función de asignación como argumentos adicionales. Tales funciones de asignación se conocen como "placement new ", en referencia a la función de asignación estándar void * operator new ( std:: size_t , void * ) , que simplemente retorna su segundo argumento sin cambios. Esto se utiliza para construir objetos en almacenamiento asignado:

// dentro de cualquier ámbito de bloque...
{
    // Asigna estáticamente el almacenamiento con duración automática
    // que es suficientemente grande para cualquier objeto de tipo “T”.
    alignas(T) unsigned char buf[sizeof(T)];
    T* tptr = new(buf) T; // Construye un objeto “T”, colocándolo directamente en tu
                          // almacenamiento preasignado en la dirección de memoria “buf”.
    tptr->~T();           // Debes llamar **manualmente** al destructor del objeto
                          // si el programa depende de sus efectos secundarios.
}                         // Salir de este ámbito de bloque desasigna automáticamente “buf”.

Nota: esta funcionalidad está encapsulada por las funciones miembro de las Allocator classes.

Al asignar un objeto cuyo requisito de alineación excede __STDCPP_DEFAULT_NEW_ALIGNMENT__ o un array de tales objetos, la expresión new pasa el requisito de alineación (envuelto en std::align_val_t ) como segundo argumento para la función de asignación (para las formas de colocación, placement-arg aparecen después de la alineación, como tercer, cuarto, etc. argumentos). Si la resolución de sobrecarga falla (lo que ocurre cuando se define una función de asignación específica de clase con una firma diferente, ya que oculta las globales), se intenta la resolución de sobrecarga por segunda vez, sin la alineación en la lista de argumentos. Esto permite que las funciones de asignación específicas de clase no conscientes de la alineación tengan prioridad sobre las funciones de asignación globales conscientes de la alineación.

(desde C++17)
new T;      // llama a operator new(sizeof(T))
            // (C++17) o operator new(sizeof(T), std::align_val_t(alignof(T))))
new T[5];   // llama a operator new[](sizeof(T)*5 + overhead)
            // (C++17) o operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T))))
new(2,f) T; // llama a operator new(sizeof(T), 2, f)
            // (C++17) o operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)

Si una función de asignación que no lanza excepciones (por ejemplo, la seleccionada por new ( std:: nothrow ) T ) devuelve un puntero nulo debido a un fallo de asignación, entonces la expresión new retorna inmediatamente; no intenta inicializar un objeto ni llamar a una función de desasignación. Si se pasa un puntero nulo como argumento a una expresión de colocación no asignadora new , lo que hace que la función de asignación de colocación estándar no asignadora seleccionada devuelva un puntero nulo, el comportamiento es indefinido.

Inicialización

El objeto creado por una expresión new se inicializa de acuerdo con las siguientes reglas.

Si type no es un tipo de array, el objeto único se construye en el área de memoria adquirida:

  • Si new-initializer es una lista de inicialización entre llaves, el objeto es list-initialized .
(desde C++11)

Si type es un tipo de array, se inicializa un array de objetos:

  • Incluso si la primera dimensión es cero, aún deben cumplirse las restricciones semánticas de la inicialización por defecto de un elemento hipotético.
  • Incluso si la primera dimensión es cero, aún deben cumplirse las restricciones semánticas de la inicialización por valor de un elemento hipotético.
(desde C++11)
(desde C++20)

Fallo de inicialización

Si la inicialización termina lanzando una excepción (por ejemplo, desde el constructor), el programa busca una función de desasignación coincidente, luego:

  • Si se puede encontrar una función de desasignación adecuada, se llama a la función de desasignación para liberar la memoria en la que se estaba construyendo el objeto. Después de eso, la excepción continúa propagándose en el contexto de la expresión new .
  • Si no se puede encontrar una función de desasignación coincidente no ambigua, propagar la excepción no causa que se libere la memoria del objeto. Solo es apropiado si la función de asignación llamada no asigna memoria, de lo contrario es probable que resulte en una pérdida de memoria.

El ámbito de la búsqueda de la función de desasignación correspondiente se determina de la siguiente manera:

  • Si la expresión new no comienza con :: , y el tipo asignado es un tipo clase T o un arreglo de tipo clase T , se realiza una búsqueda del nombre de la función de desasignación en el ámbito de la clase de T .
  • De lo contrario, o si no se encuentra nada en el ámbito de la clase de T , el nombre de la función de desasignación se busca examinando el ámbito global .

Para una función de asignación sin colocación, se utiliza la búsqueda normal de función de desasignación para encontrar la función de desasignación correspondiente (ver delete-expression ).

Para una función de asignación de colocación, la función de desasignación correspondiente debe tener el mismo número de parámetros, y cada tipo de parámetro excepto el primero es idéntico al tipo de parámetro correspondiente de la función de asignación (después de transformaciones de parámetros ).

  • Si la búsqueda encuentra una única función de desasignación coincidente, esa función será llamada; de lo contrario, no se llamará a ninguna función de desasignación.
  • Si la búsqueda encuentra una función de desasignación no de colocación y esa función, considerada como una función de desasignación de colocación, habría sido seleccionada como coincidencia para la función de asignación, el programa está mal formado.

En cualquier caso, la función de desasignación correspondiente (si existe) debe ser no eliminada y (desde C++11) accesible desde el punto donde aparece la expresión new .

struct S
{
    // Función de asignación de colocación:
    static void* operator new(std::size_t, std::size_t);
    // Función de desasignación no de colocación:
    static void operator delete(void*, std::size_t);
};
S* p = new (0) S; // error: la función de desasignación no de colocación coincide
                  //        con la función de asignación de colocación

Si una función de desasignación es llamada en una new expresión (debido a un fallo de inicialización), los argumentos pasados a esa función se determinan de la siguiente manera:

  • El primer argumento es el valor (de tipo void * ) devuelto desde la llamada a la función de asignación.
  • Otros argumentos (solo para funciones de desasignación de colocación) son los placement-args pasados a la función de asignación de colocación.

Si la implementación tiene permitido introducir un objeto temporal o hacer una copia de cualquier argumento como parte de la llamada a la función de asignación, no está especificado si se utiliza el mismo objeto en la llamada tanto a las funciones de asignación como de desasignación.

Fugas de memoria

Los objetos creados por new expressions (objetos con duración de almacenamiento dinámico) persisten hasta que el puntero devuelto por la new expression se utiliza en una delete-expression coincidente. Si el valor original del puntero se pierde, el objeto se vuelve inaccesible y no puede ser desasignado: ocurre un memory leak .

Esto puede suceder si el puntero se asigna a:

int* p = new int(7); // int asignado dinámicamente con valor 7
p = nullptr; // fuga de memoria

o si el puntero sale del ámbito:

void f()
{
    int* p = new int(7);
} // fuga de memoria

o debido a una excepción:

void f()
{
    int* p = new int(7);
    g();      // puede lanzar una excepción
    delete p; // correcto si no hay excepción
} // pérdida de memoria si g() lanza una excepción

Para simplificar la gestión de objetos asignados dinámicamente, el resultado de una expresión new frecuentemente se almacena en un smart pointer : std::auto_ptr (until C++17) std::unique_ptr , o std::shared_ptr (since C++11) . Estos punteros garantizan que la expresión delete se ejecute en las situaciones mostradas anteriormente.

Notas

Itanium C++ ABI requiere que la sobrecarga de asignación de arreglos sea cero si el tipo de elemento del arreglo creado es trivialmente destructible. MSVC también lo requiere.

Algunas implementaciones (por ejemplo, MSVC antes de VS 2019 v16.7) requieren sobrecarga de asignación de arreglo no nula en el placement array no asignador new si el tipo de elemento no es trivialmente destructible, lo que ya no es conforme desde CWG issue 2382 .

Una expresión de creación de arreglo con colocación sin asignación new que crea un arreglo de unsigned char , o std::byte (desde C++17) puede utilizarse para crear objetos implícitamente en una región determinada de almacenamiento: finaliza el tiempo de vida de los objetos que se superponen con el arreglo, y luego crea implícitamente objetos de tipos de duración implícita en el arreglo.

std::vector ofrece funcionalidad similar para arreglos dinámicos unidimensionales.

Palabras clave

new

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 74 C++98 el valor en la primera dimensión debía tener tipo integral se permiten tipos de enumeración
CWG 299 C++98 el valor en la primera dimensión debía
tener tipo integral o de enumeración
se permiten tipos de clase con una única
función de conversión a tipo integral
o de enumeración
CWG 624 C++98 el comportamiento no estaba especificado cuando
el tamaño del objeto asignado excedía
el límite definido por la implementación
no se obtiene almacenamiento y se
lanza una excepción en este caso
CWG 1748 C++98 el placement new no asignador
necesitaba verificar si el argumento era nulo
comportamiento indefinido para argumento nulo
CWG 1992 C++11 new ( std:: nothrow ) int [ N ]
podría lanzar std::bad_array_new_length
cambiado para retornar un puntero nulo
CWG 2102 C++98 no estaba claro si la inicialización por defecto/por valor
debía estar bien formada al inicializar arrays vacíos
requerido
CWG 2382 C++98 el placement array new no asignador
podría requerir sobrecarga de asignación
dicha sobrecarga de asignación no permitida
CWG 2392 C++11 el programa podría estar mal formado incluso si
la primera dimensión no está potencialmente evaluada
bien formado en este caso
P1009R2 C++11 el límite del array no podía ser
deducido en una expresión new
deducción permitida

Véase también