Operador new

Operador new
Información sobre la plantilla
Concepto:Proporciona espacio de almacenamiento persistente, similar pero superior a la función de Librería Estándar malloc. Este operador permite crear un objeto de cualquier tipo, incluyendo tipos definidos por el usuario, y devuelve un puntero (del tipo adecuado) al objeto creado
Operador new. Proporciona espacio de almacenamiento persistente, similar pero superior a la función de Librería Estándar malloc. Este operador permite crear un objeto de cualquier tipo, incluyendo tipos definidos por el usuario, y devuelve un puntero (del tipo adecuado) al objeto creado. Su utilización exige que el usuario declarare un puntero del tipo adecuado; a continuación debe ser inicializado con el valor devuelto por el operador.

Sintaxis

<::> new <(situación)>  tipoX  <(iniciador)>
<::> new <(situación)> (tipoX) <(iniciador)>
  • El argumento tipoX es imprescindible. Indica el tipo de objeto para el que se obtendrá espacio de almacenamiento. Ejemplo:
ClaseA* ptr = new ClaseA;

Si la especificación de tipoX es complicada, se permite englobarla en paréntesis para facilitar al compilador la correcta interpretación (segunda forma de la sintaxis). Ejemplo: el tipo de una matriz de diez punteros a función que no reciben argumentos y devuelven un int es: int(*[10])(). Sin embargo, la invocación a su construcción con new:

new int(*[10])();        // Error

produce un error, ya que el compilador es incapaz de interpretar correctamente una sentencia como la anterior; en su lugar interpretaría:

(new int) (*[10])();     // Error

Para evitar el error es necesario incluir la expresión en paréntesis:

new (int (*[10])());     // Ok.

El resto de argumentos, opcionales, son los siguientes:

  • <::> Operador que invoca la versión global de new. Este argumento opcional se utiliza cuando existe una versión específica de usuario (sobrecargada) pero se desea utilizar la versión global (en caso contrario no es necesario).
  • <(situación)>, este especificador opcional permite especificar el sitio concreto en que se realizará la reserva de memoria. Ejemplo:
tipoX* xp = new (z) tipoX;   // definir sitio de almacenamiento
  • <(iniciador)>, en su caso, se utiliza para inicializar el almacenamiento. Ejemplo:
tipoX* xp = new tipoX (z);   // definir valor inicial

Descripción

En la expresión

new ClaseC;

El operador new intenta asignar un espacio de tamaño sizeof(ClaseC) en la zona de memoria dinámica. A continuación intenta crear en esta posición una instancia de la clase utilizando el constructor adecuado. Como resultado de estas dos operaciones se obtiene la dirección (puntero) del objeto creado. Este puntero devuelto por new es del tipo correcto: puntero-a-ClaseC, sin que se necesaria ninguna conversión de tipo ("casting") explícita. Aunque el operador tiene algunas limitaciones, puede utilizarse con tipos calificados. Por ejemplo, es válido:

new const ClaseC;

Los objetos creados con new son persistentes, es decir, la vida del nuevo objeto es desde el punto de creación hasta el final del programa o hasta que el programador lo destruya explícitamente con el operador delete. Este último desasigna la zona de memoria ocupada por el objeto, de forma que queda disponible para nuevo uso. Las sucesivas invocaciones de este operador van reservando zonas de memoria en el montón para los objetos sucesivamente creados. El gestor de memoria del compilador se encarga de mantener una tabla con los sitios ocupados y libres sin que haya conflictos hasta que la memoria se ha agota, o no existe espacio contiguo suficiente para el nuevo objeto. En cuyo caso se lanza una excepción como indicativo del error. El operador new puede aceptar un inicializador opcional para que rellene el espacio reservado con el valor suministrado. Sin embargo, su versión para matrices new[ ] no acepta iniciador. En caso de no proporcionarse iniciador, el objeto creado contiene basura. Recordar que:

  • Los objetos creados con new deben ser destruidos necesariamente con delete, y que las matrices creadas con new[ ] deben ser borradas con delete[ ].
  • Los objetos estáticos aunque son de carácter permanente, tienen su propia zona de almacenamiento distinta del montón, por lo que no se crean con este operador.
  • Por razones evidentes, no es posible crear un objeto de un tipo ClaseC que sea una clase abstracta.

La invocación del operador new en una sentencia como

ClaseC* cPtr = new ClaseC;

implica tres operaciones que son realizadas automáticamente:

  1. Se reserva espacio suficiente en el montón para el objeto. new utiliza el operador sizeof para determinar el espacio adecuado a reservar. A continuación invoca al constructor del objeto (ver punto 3º ).
  2. Se crea un puntero cPtr adecuado al tipo (en este caso su tipo es puntero-a-ClaseC). Este puntero se inicia con el valor de la dirección del espacio reservado en el punto anterior.
  3. Si se utiliza un iniciador opcional, new invoca el constructor correspondiente (que debe corresponder con los argumentos utilizados); en caso contrario se utiliza el constructor por defecto. Así pues, si tipoX es un tipo abstracto, definido por el usuario, debe existir un constructor por defecto (que pueda ser invocado sin argumentos, pues new lo utilizará para crear el objeto cuando sea invocado sin especificador de inicio.

Peligros

La persistencia de los objetos creados con new y su independencia del ámbito desde el que han sido creados, es muy importante y de tener en cuenta, pues suele ser motivo de pérdidas de memoria en el programa si olvidamos destruirlos cuando ya no son necesarios. Hay que prestar especial atención, porque en una sentencia como:

void func() {
 ...
 tipoX* Xptr = new tipoX;
 ...
}

El área de almacenamiento señalada por el puntero es persistente, pero Xptr que es una variable local automática no lo es. Si olvidamos destruir el objeto creado (con delete) antes de salir del ámbito, el área de almacenamiento permanecerá ocupando espacio en el montón y no podrá ser recuperada nunca, pues el puntero Xptr habrá desaparecido.

Limitaciones

En la especificación del tipo de objeto que debe crearse con new, no están permitidos los especificadores de almacenamiento static, auto y register. Tampoco typedef. Ejemplo:

int* iptr = new static int;        // Error!!
int* iptr = new typedef int;       // Error!!
int* iptr = new auto int;          // Error!!
int* iptr = new register int;      // Error!!

Tampoco es posible crear referencias con new, ya que las referencias no son objetos sino nombres alternativos (alias) de objetos.

Control de la operación

Si la operación tiene éxito, new devuelve un puntero no nulo (distinto de cero) al objeto. Si la asignación falla (como en caso de no haber suficiente espacio en memoria dinámica), se lanza la excepción bad_alloc, a menos que se defina un nuevo manejador de excepciones. Antes de intentar usar el nuevo objeto, el programa debe estar siempre preparado para capturar dicha excepción Puesto que la invocación de new con éxito devuelve un puntero no nulo, la petición de espacio para 0 bytes devuelve un puntero "no nulo", y las peticiones sucesivas en este sentido, devuelven punteros "no nulos" distintos.

Asignar el valor devuelto

La forma usual de utilizar el operador new es en sentencias de asignación. Puesto que new devuelve un puntero, es utilizado en el lado derecho (Rvalue) de la asignación. En el lado izquierdo (Lvalue) debe existir un puntero de tipo adecuado para recibir el valor devuelto. Preste atención a las declaraciones de punteros de los siguientes ejemplos:

UnaClase*  punt1 = new UnaClase;    // Ok.
int* punt2 = new int;               // Ok.
char* punt3 = new int;              // Ilegal
char* punt4 = new char;             // Ok.

Véase también

Fuente