Creación dinámica de objetos: nuevo y suprimir

Hasta ahora sólo hemos intentado crear objetos automáticos, es decir, variables locales dentro de OnStart. Un objeto declarado en el contexto global (fuera de OnStart o de alguna otra función) también se crearía automáticamente (cuando se carga el script) y se eliminaría (cuando se descarga el script).

Además de estos dos modos, hemos mencionado la posibilidad de describir un campo de un tipo de objeto (en nuestro ejemplo, se trata de la estructura Pair utilizada para el campo coordinates dentro del objeto Shape). Todos estos objetos son también automáticos: los crea para nosotros un compilador en un constructor de un objeto «anfitrión» y los elimina en su destructor.

Sin embargo, a menudo es imposible arreglárselas sólo con objetos automáticos en los programas. En el caso de un programa de dibujo, necesitaremos crear formas a petición del usuario. Además, las formas tendrán que almacenarse en un array, y para ello los objetos automáticos tendrían que tener un constructor por defecto (cosa que no ocurre en nuestro caso).

Para estas situaciones, MQL5 ofrece la posibilidad de crear y eliminar objetos de forma dinámica. La creación se realiza con el operador new y la eliminación, con el operador delete.

 

Operadornew

La palabra clave new va seguida del nombre de la clase requerida y, entre paréntesis, una lista de argumentos para llamar a cualquiera de los constructores existentes. La ejecución del operador new conduce a la creación de una instancia de la clase.

El operador new devuelve un valor de un tipo especial: un puntero a un objeto. Para describir una variable de este tipo, añada un asterisco '*' después del nombre de la clase. Por ejemplo:

Rectangle *pr = new Rectangle(1002005075clrBlue);

Aquí la variable pr tiene un tipo de puntero a un objeto de la clase Rectangle. Los punteros se tratarán con más detalle en una sección aparte.

Es importante señalar que la declaración de una variable de tipo puntero de objeto en sí no asigna memoria para un objeto y no invoca a su constructor. Por supuesto, un puntero ocupa espacio (8 bytes), pero se trata de un entero sin signo ulong que el sistema interpreta de una manera especial.

Puede trabajar con un puntero del mismo modo que con un objeto, es decir, puede invocar los métodos disponibles mediante el operador de desreferenciación y acceder a los campos.

Print(pr.toString());

La variable de un puntero a la que todavía no se le ha asignado un descriptor de objeto dinámico (por ejemplo, si el operador new no se invoca en el momento de la inicialización de una nueva variable, sino que se traslada a algunas líneas posteriores del código fuente), contiene un puntero nulo especial, que se denota como NULL (para distinguirlo de los números) pero que en realidad es igual a 0.

 

Operadordelete

Los punteros recibidos a través de new deben liberarse al final de un algoritmo utilizando el operador delete. Por ejemplo:

delete pr;

Si no se hace así, la instancia asignada por el operador new permanecerá en memoria. Si se crean cada vez más objetos nuevos de esta forma y luego no se borran cuando ya no se necesitan, se producirá un consumo innecesario de memoria. El resto de objetos dinámicos no liberados hacen que se impriman avisos cuando el programa termina. Por ejemplo, si no borra el puntero pr, obtendrá algo como esto en el registro una vez descargado el script:

1 undeleted object left
1 object of type Rectangle left
168 bytes of leaked memory

El terminal informa de cuántos objetos y de qué clase fueron olvidados por el programador, así como de cuánta memoria ocupaban.

Una vez que se llama al operador delete para un puntero, éste se invalida porque el objeto ya no existe. Un intento posterior de acceder a sus propiedades provoca un error en tiempo de ejecución "Puntero no válido accedido":

Critical error while running script 'shapes (EURUSD,H1)'.
Invalid pointer access.

El programa MQL se interrumpe.

Sin embargo, esto no significa que ya no se pueda utilizar la misma variable de puntero. Basta con asignar un puntero a otra instancia recién creada del objeto.

MQL5 tiene una función integrada que le permite comprobar la validez de un puntero en una variable; se trata de CheckPointer:

ENUM_POINTER_TYPE CheckPointer(object *pointer);

Toma un parámetro de un puntero a una clase de tipo y devuelve un valor de la enumeración ENUM_POINTER_TYPE:

  • POINTER_INVALID: puntero incorrecto;
  • POINTER_DYNAMIC: puntero válido a un objeto dinámico;
  • POINTER_AUTOMATIC: puntero válido a un objeto automático.

La ejecución de la sentencia delete sólo tiene sentido para un puntero para el que la función haya devuelto POINTER_DYNAMIC. Para un objeto automático no tendrá ningún efecto (tales objetos se borran automáticamente cuando el control vuelve del bloque de código en el que se ha definido la variable).

La siguiente macro simplifica y garantiza la correcta limpieza de un puntero:

#define FREE(Pif(CheckPointer(P) == POINTER_DYNAMICdelete (P)

La necesidad de «limpiar» explícitamente es un precio inevitable que hay que pagar por la flexibilidad que proporcionan los objetos dinámicos y los punteros.