- Encabezado de plantilla
- Principios generales de funcionamiento de la plantilla
- Plantillas frente a macros de preprocesador
- Características de tipos integrados y objetos en plantillas
- Plantillas de funciones
- Plantillas de tipos de objetos
- Plantillas de métodos
- Plantillas anidadas
- Ausencia de especialización de plantillas
Plantillas de tipos de objetos
Una definición de plantilla de tipo de objeto comienza con un encabezado que contiene parámetros con tipos (véase la sección Encabezado de plantilla) y la definición habitual de una clase, estructura o unión.
template <typename T [, typename Ti ...] >
|
La única diferencia con respecto a la definición estándar es que los parámetros de plantilla pueden aparecer en un bloque de código, en todas las construcciones sintácticas del lenguaje en las que está permitido utilizar un nombre de tipo.
Una vez definida una plantilla, se crean instancias de trabajo de la misma cuando las variables del tipo de plantilla se declaran en el código, especificando los tipos específicos entre paréntesis angulares:
ClassName<Type1,Type2> object;
|
A diferencia de lo que ocurre al llamar a funciones de plantilla, el compilador no es capaz de inferir por sí mismo los tipos reales de las plantillas de objetos.
Declarar una variable de clase/estructura de plantilla no es la única forma de crear una instancia de plantilla. El compilador también genera una instancia si se utiliza un tipo de plantilla como tipo base para otra clase o estructura específica (no de plantilla).
Por ejemplo, la siguiente clase Worker, aunque esté vacía, es una implementación de Base para el tipo double:
class Worker : Base<double>
|
Esta definición mínima es suficiente (con la posibilidad de añadir constructores si la clase Base los requiere) para empezar a compilar y validar el código de la plantilla.
En la sección sobre Creación dinámica de objetos nos hemos familiarizado con el concepto de puntero dinámico a un objeto obtenido mediante el operador new. Este mecanismo flexible tiene un inconveniente: hay que controlar los punteros y borrarlos «manualmente» cuando ya no se necesitan. En particular, al salir de una función o bloque de código, todos los punteros locales deben borrarse con una llamada delete.
Para simplificar la solución a este problema, vamos a crear una clase de plantilla AutoPtr (TemplatesAutoPtr.mq5, AutoPtr.mqh). Su parámetro T se utiliza para describir el campo ptr, que almacena un puntero a un objeto de una clase arbitraria. Recibiremos el valor del puntero a través del parámetro del constructor (T *p) o en el operador sobrecargado '='. Confiemos el trabajo principal al destructor: en el destructor, el puntero se borrará junto con el objeto AutoPtr (para ello se asigna el método del ayudante estático free).
El principio de funcionamiento de AutoPtr es sencillo: un objeto local de esta clase se destruirá automáticamente al salir del bloque en el que está descrito, y si previamente se le ordenó «seguir» algún puntero, entonces AutoPtr también lo liberará.
template<typename T>
|
Además, la clase AutoPtr implementa un constructor de copia (más exactamente, un constructor de salto, ya que el objeto actual se convierte en el propietario del puntero), que permite devolver una instancia de AutoPtr junto con un puntero controlado desde una función.
Para probar el rendimiento de AutoPtr vamos a describir una clase Dummy ficticia.
class Dummy
|
En el script, en la función OnStart, introduzca la variable AutoPtr<Dummy> y obtenga su valor de la función generator. En la propia función generator describiremos también el objeto AutoPtr<Dummy> y crearemos y le «adjuntaremos» secuencialmente dos objetos dinámicos Dummy (para comprobar la correcta liberación de memoria del objeto «antiguo»).
AutoPtr<Dummy> generator()
|
Dado que todos los métodos principales registran descriptores de objetos (tanto AutoPtr como punteros controlados ptr), podemos realizar un seguimiento de todas las «transformaciones» de punteros (por comodidad, todas las líneas están numeradas).
01 Dummy::Dummy(int) 3145728
|
Dejemos por un momento de lado las plantillas y describamos en detalle cómo funciona la utilidad, porque una clase así puede ser muy útil.
Inmediatamente después de iniciar OnStart se llama al generador de funciones. Éste debe devolver un valor para inicializar el objeto AutoPtr de OnStart, y por lo tanto su constructor aún no ha sido invocado. La línea 02 crea un objeto AutoPtr#2097152 dentro de la función generator y obtiene un puntero al primer Dummy#3145728. A continuación, se crea una segunda instancia de Dummy#4194304 (línea 03) que sustituye a la copia anterior con el descriptor 3145728 (línea 04) en AutoPtr#2097152, y se borra la copia antigua (línea 05). La línea 06 crea un AutoPtr#5242880 temporal para devolver el valor del generator, y borra el local (07). En la línea 08 se utiliza finalmente el constructor de copia para el objeto AutoPtr#1048576 en la función OnStart y se le transfiere el puntero del objeto temporal (que se borra inmediatamente en la línea 09). A continuación, llamamos a Print con el contenido del puntero. Cuando OnStart finaliza, el destructor AutoPtr (11) se dispara automáticamente, haciendo que también borremos el objeto de trabajo Dummy (12).
La tecnología de plantillas convierte la clase AutoPtr en un gestor parametrizado de objetos asignados dinámicamente. Pero como AutoPtr tiene un campo T *ptr, sólo se aplica a las clases (más concretamente, a los punteros a objetos de clase). Por ejemplo, intentar crear una instancia de una plantilla para una cadena (AutoPtr<string> s) dará como resultado un montón de errores en el texto de la plantilla, cuyo significado es que el tipo string no admite punteros.
Esto no es un problema aquí, ya que el propósito de esta plantilla se limita a las clases, pero es un matiz que debe tenerse en cuenta para plantillas más generales (véase la barra lateral).
Punteros y referencias
Tenga en cuenta que la construcción T * no puede aparecer en plantillas que tenga previsto utilizar, incluidas las de estructuras o tipos integrados. La cuestión es que en MQL5, los punteros sólo se permiten para las clases. Esto no quiere decir que, en teoría, no se pueda escribir una plantilla que se aplique tanto a los tipos integrados como a los definidos por el usuario, pero puede requerir algunos ajustes. Probablemente será necesario abandonar parte de la funcionalidad o sacrificar un nivel de genericidad de la plantilla (hacer varias plantillas en lugar de una, sobrecargar funciones, etc.).
La forma más directa de «inyectar» un tipo de puntero en una plantilla es incluir el modificador '*' junto con el tipo real cuando se crea una instancia de la plantilla (es decir, debe coincidir con T=Type*). Sin embargo, algunas funciones (como CheckPointer), operadores (como delete) y construcciones sintácticas (como conversión ((T)variable)) son sensibles a si sus argumentos u operandos son punteros o no. Por ello, el mismo texto de plantilla no siempre es sintácticamente correcto tanto para punteros como para valores de tipo simple.
Otra diferencia significativa entre tipos que hay que tener en cuenta es que los objetos se pasan a los métodos sólo por referencia, pero los literales (constantes) de tipos simples no pueden pasarse por referencia. Debido a ello, la presencia o ausencia de un ampersand puede ser tratada como un error por el compilador, dependiendo del tipo inferido de T. Como una de las «soluciones», puede optar por «envolver» constantes de argumento en objetos o variables.
Otro truco consiste en utilizar métodos de plantilla. Lo veremos en la siguiente sección.
Cabe señalar que las técnicas orientadas a objetos combinan bien con los patrones. Dado que un puntero a una clase base puede utilizarse para almacenar un objeto de una clase derivada, AutoPtr es aplicable a objetos de cualquier clase derivada Dummy.
En teoría, este enfoque «híbrido» se utiliza ampliamente en las clases contenedoras (vector, cola, mapa, lista, etc.), que, por regla general, son plantillas. Las clases contenedoras pueden, dependiendo de la implementación, imponer requisitos adicionales al parámetro de plantilla; en concreto, que el tipo inline debe tener un constructor de copia y un operador de asignación (copia).
La biblioteca estándar MQL5 suministrada con MetaTrader 5 contiene muchas plantillas ya preparadas de esta serie: Stack.mqh, Queue.mqh, HashMap.mqh, LinkedList.mqh, RedBlackTree.mqh, y otras. Todas ellas se encuentran en el directorio MQL5/Include/Generic. Es cierto que no proporcionan control sobre objetos dinámicos (punteros).
Veremos nuestro propio ejemplo de una sencilla clase contenedora en Plantillas de métodos.