new
expression
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) | ||||||||
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
- type es un array de límite desconocido ,
|
(desde C++11) |
|
(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:
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 está ausente, el objeto es default-initialized .
- Si new-initializer es una lista de expresiones entre paréntesis, el objeto es direct-initialized .
|
(desde C++11) |
Si type es un tipo de array, se inicializa un array de objetos:
- Si new-initializer está ausente, cada elemento es default-initialized .
-
- 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.
- Si new-initializer es un par de paréntesis, cada elemento es value-initialized .
-
- 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 claseTo un arreglo de tipo claseT, se realiza una búsqueda del nombre de la función de desasignación en el ámbito de la clase deT. -
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
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 |