c++ - Escalar `nueva T` frente a matriz` nueva T[1] `
new-operator (3)
Recientemente descubrimos que parte del código estaba usando el
new T[1]
sistemáticamente (correctamente emparejado para
delete[]
), y me pregunto si esto es inofensivo o si hay algunas desventajas en el código generado (en espacio o tiempo / rendimiento) .
Por supuesto, esto estaba oculto detrás de capas de funciones y macros, pero eso no viene al caso.
Lógicamente, me parece que ambos son similares, pero ¿lo son?
¿Se les permite a los compiladores convertir este código (usando un literal 1, no una variable, sino a través de capas de funciones, que
1
convierte en una variable de argumento 2 o 3 veces antes de llegar al código usando la
new T[n]
) en una
new T
escalar ?
¿Alguna otra consideración / cosas que debe saber sobre la diferencia entre estos dos?
La regla es simple:
delete[]
debe coincidir con
new[]
y
delete
debe coincidir con
new
: el comportamiento al usar cualquier otra combinación es indefinido.
De hecho, el compilador permite convertir la
new T[1]
en una
new T
simple (y lidiar con la
delete[]
apropiada), debido a la regla
as-if
.
Sin embargo, no he encontrado un compilador que haga esto.
Si tiene alguna reserva sobre el rendimiento, perfílelo.
No, el compilador no puede reemplazar la
new T[1]
con una
new T
operator new
y
operator new[]
(y las eliminaciones correspondientes) son
reemplazables
([basic.stc.dynamic] / 2).
Un reemplazo definido por el usuario podría detectar cuál se llama, por lo que la regla as-if no permite este reemplazo.
Nota: si el compilador puede detectar que estas funciones no han sido reemplazadas, podría hacer ese cambio. Pero no hay nada en el código fuente que indique que las funciones proporcionadas por el compilador están siendo reemplazadas. El reemplazo generalmente se realiza en el momento del enlace , simplemente vinculando las versiones de reemplazo (que ocultan la versión suministrada por la biblioteca); generalmente es demasiado tarde para que el compilador lo sepa.
Si
T
no tiene un destructor trivial, entonces para las implementaciones habituales del compilador, la
new T[1]
tiene una sobrecarga en comparación con la
new T
La versión de matriz asignará un área de memoria un poco más grande, para almacenar el número de elementos, por lo que en
delete[]
, sabe cuántos destructores deben llamarse.
Entonces, tiene una sobrecarga:
- se debe asignar un área de memoria un poco más grande
-
delete[]
será un poco más lento, ya que necesita un bucle para llamar a los destructores, en lugar de llamar a un destructor simple (aquí, la diferencia es la sobrecarga del bucle)
Mira este programa:
#include <cstddef>
#include <iostream>
enum Tag { tag };
char buffer[128];
void *operator new(size_t size, Tag) {
std::cout<<"single: "<<size<<"/n";
return buffer;
}
void *operator new[](size_t size, Tag) {
std::cout<<"array: "<<size<<"/n";
return buffer;
}
struct A {
int value;
};
struct B {
int value;
~B() {}
};
int main() {
new(tag) A;
new(tag) A[1];
new(tag) B;
new(tag) B[1];
}
En mi máquina, imprime:
single: 4
array: 4
single: 4
array: 12
Debido a que
B
tiene un destructor no trivial, el compilador asigna 8 bytes adicionales para almacenar el número de elementos (debido a que es una compilación de 64 bits, necesita 8 bytes adicionales para hacer esto) para la versión de matriz.
Como
A
destructor trivial, la versión de matriz de
A
no necesita este espacio extra.