Del básico al intermedio: Clases (II)
Introducción
En el artículo anterior Del básico al intermedio: Clases (I), empezamos a abordar la programación orientada a objetos. Sin embargo, allí solo hablamos de los constructores de clases. A veces necesitamos implementar otra función miembro especial propia de las clases: el destructor.
Para comprender correctamente cómo funcionan los destructores, podemos establecer un paralelismo entre la programación orientada a objetos, que crea y utiliza clases, y la programación basada en eventos de MetaTrader 5. ¡Vaya! ¿Qué comparación tan extraña pretendes hacer? Tranquilo, mi querido lector. Pronto lo entenderás. Te aseguro que así podrás asimilar mucho mejor por qué existen los destructores y cómo debes implementarlos cuando sea necesario.
Ha llegado el momento de dejar de lado cualquier cosa que pueda distraerte y concentrarte en lo que veremos en este artículo. Dicho esto, pasemos al siguiente tema.
Clases (II)
Uno de los conceptos más difíciles de aprender en programación orientada a objetos es el destructor. En la mayoría de los casos, los destructores simplemente no tienen ningún sentido. Además, como tú, en calidad de programador, no puedes llamar explícitamente a un destructor, resulta aún más complicado comprender por qué existe. Esto no ayuda en absoluto a que un programador principiante comprenda el concepto de destructor.
Los programadores de la vieja escuela, como yo, que literalmente vimos evolucionar la programación a lo largo de los años, también tuvimos dificultades para comprender algunos de los conceptos y mecanismos que fueron surgiendo. Ahora imagina a un programador que se enfrenta a todos esos conceptos ya implementados y necesita comprenderlos. ¡Menuda confusión! Sobre todo porque muchos de estos conceptos suelen explicarse muy mal y convierten algo que en principio es sencillo en un monstruo parecido a la hidra: cuanto más le cortas las cabezas para acabar con él, más complicada se vuelve la situación. El destructor es precisamente uno de esos conceptos, En este artículo entenderás que, aunque parezca complicado, en realidad es muy sencillo. Cuando comprendas correctamente su principio de funcionamiento, te resultará mucho más fácil saber cuándo y cómo implementarlo en tus clases.
Muy bien, para empezar, olvidemos por un momento la programación orientada a objetos y pasemos a algo más sencillo y práctico: la programación orientada a eventos. Este es el paradigma que utilizamos cuando creamos indicadores y Asesores Expertos para MetaTrader 5. Bien, pero ¿qué relación existe entre la programación orientada a eventos y la programación orientada a objetos? Para ser sincero, mi querido lector, no tienen demasiado en común. Sin embargo, como utilizamos MQL5, un lenguaje diseñado para permitir que el programador controle el funcionamiento de MetaTrader 5, resulta muy sencillo establecer una relación entre ambos paradigmas. Esta comparación nos permitirá demostrar cómo funciona un destructor en la práctica.
Para empezar, crearemos un indicador muy sencillo y básico, cuyo código se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. int OnInit() 06. { 07. return INIT_SUCCEEDED; 08. }; 09. //+------------------------------------------------------------------+ 10. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 11. { 12. return rates_total; 13. }; 14. //+------------------------------------------------------------------+ 15. void OnDeinit(const int reason) 16. { 17. }; 18. //+------------------------------------------------------------------+
Código 01
Como vimos en los primeros artículos sobre indicadores de esta misma serie, tanto los indicadores como los Asesores Expertos funcionan mediante eventos, que, en la mayoría de los casos, proporciona y dispara MetaTrader 5. También existen situaciones en las que el propio código puede disparar eventos, pero eso no es relevante ahora. Lo importante es comprender que estos eventos proceden de MetaTrader 5.
Para obtener más detalles, consulta los artículos anteriores. En particular, Del básico al intermedio: Eventos (I) te ayudará a comprender algunos de los conceptos necesarios para seguir este artículo.
Podemos considerar el código 01 como si fuera una clase completa. ¿Cómo es eso? No entiendo lo que quieres decir. Tranquilo, enseguida lo comprenderás. ¿Recuerdas que en el artículo anterior hablamos de dos funciones miembro especiales de una clase? El constructor se encarga de inicializar la clase, mientras que el destructor se encarga de destruir lo que la clase ha construido. Pues bien, observa de nuevo el código 01. Como se trata de un indicador, cuando MetaTrader 5 lo coloque en el gráfico, primero llamará y ejecutará el código de la función OnInit. Cuando retiremos ese mismo indicador del gráfico, MetaTrader 5 llamará y ejecutará la función OnDeinit, siempre que esté implementada. De este modo, podremos eliminar del gráfico cualquier elemento que queramos retirar una vez eliminado el indicador.
A partir de este sencillo concepto y de la similitud entre la programación orientada a objetos y el funcionamiento del código del indicador, podemos implementar algo en el código 01 y empezar a comprender cómo funciona realmente un destructor.
Añadiremos un objeto al gráfico y, cuando se retire el indicador, eliminaremos también ese objeto. Es una tarea muy sencilla que ya se explicó en otros artículos de esta misma serie.
Como base para lo que haremos, utilizaremos el artículo Del básico al intermedio: Objetos (I). En él mostré cómo añadir y eliminar objetos del gráfico. Al modificar el código 01 para aplicar lo explicado en ese artículo, obtenemos el código que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. int OnInit() 08. { 09. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, 0, 0); 10. 11. return INIT_SUCCEEDED; 12. }; 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. return rates_total; 17. }; 18. //+------------------------------------------------------------------+ 19. void OnDeinit(const int reason) 20. { 21. ObjectDelete(0, def_NameChannel); 22. ChartRedraw(); 23. }; 24. //+------------------------------------------------------------------+
Código 02
El objetivo del código 02 es crear un objeto OBJ_REGRESSION y mostrarlo en el gráfico. En cuanto se retire el indicador, el objeto también se eliminará. Sin embargo, al ejecutar el código notarás que no aparece ningún objeto en el gráfico, aunque la línea nueve sí lo crea. ¿Cómo podemos resolverlo? Es muy sencillo, mi querido lector: basta con modificar el código como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. int OnInit() 08. { 09. datetime dt0 = TimeCurrent(), 10. dt1[20]; 11. 12. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 13. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 14. 15. return INIT_SUCCEEDED; 16. }; 17. //+------------------------------------------------------------------+ 18. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 19. { 20. return rates_total; 21. }; 22. //+------------------------------------------------------------------+ 23. void OnDeinit(const int reason) 24. { 25. ObjectDelete(0, def_NameChannel); 26. ChartRedraw(); 27. }; 28. //+------------------------------------------------------------------+
Código 03
El único cambio necesario consiste en obtener las fechas que utilizaremos para crear y posicionar el objeto de regresión. Esto se hace en las líneas nueve y doce. El resultado se muestra a continuación.

Imagen 01
Ahora presta atención al siguiente detalle. Cuando añadimos este indicador al gráfico, se crea y posiciona el objeto definido en la línea 13. Al retirar el indicador, ese mismo objeto se elimina del gráfico gracias a la línea 25. Es decir, el manejador de eventos OnInit hace las veces de constructor y el manejador de eventos OnDeinit hace las veces de destructor. De acuerdo, pero ¿qué relación tiene esto con la programación orientada a objetos? Todavía no entiendo adónde quieres llegar.
Bien, mi querido lector, ahora lo comprenderás, porque hemos llegado a la parte divertida: transformar el código 03 en un programa que utilice programación orientada a objetos. Presta mucha atención para entender cómo lo haremos. Como el código 03 es un indicador y para aplicar programación orientada a objetos en él necesitaríamos utilizar dos instrucciones que aún no hemos explicado, cambiaremos el enfoque y transformaremos el código 03 en un script. El resultado se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. datetime dt0 = TimeCurrent(), 09. dt1[20]; 10. 11. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 12. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 13. ChartRedraw(); 14. 15. Sleep(2000); 16. 17. ObjectDelete(0, def_NameChannel); 18. ChartRedraw(); 19. } 20. //+------------------------------------------------------------------+
Código 04
Observa que ahora utilizamos un script en lugar de un indicador. Sin embargo, esto no cambia lo que seguimos haciendo. Solo adoptamos un enfoque en el que será más sencillo explicar y visualizar la ejecución del constructor y del destructor de la clase que crearemos más adelante. Para evitar que el objeto desaparezca inmediatamente después de crearlo, utilizamos la línea quince para introducir una pequeña pausa entre su colocación y su eliminación. Al ejecutar el código, este se comportará como muestra la siguiente animación.

Animación 01
Perfecto, el script funciona correctamente. Ahora comienza la parte divertida. Solo tenemos que modificar el código 04 como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. class C_Regression 07. { 08. private : 09. //+----------------+ 10. public : 11. //+----------------+ 12. C_Regression() 13. { 14. datetime dt0 = TimeCurrent(), 15. dt1[20]; 16. 17. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 18. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 19. ChartRedraw(); 20. } 21. //+----------------+ 22. }; 23. //+------------------------------------------------------------------+ 24. void OnStart(void) 25. { 26. C_Regression channel; 27. 28. Sleep(2000); 29. 30. ObjectDelete(0, def_NameChannel); 31. ChartRedraw(); 32. } 33. //+------------------------------------------------------------------+
Código 05
Ahora presta mucha atención, mi querido lector. El código 05 producirá el mismo resultado que vimos en la animación 01. Sin embargo, aquí empezamos a ver cómo implementar este comportamiento mediante programación orientada a objetos. Como la parte relacionada con el constructor se explicó en el artículo anterior, podemos centrarnos en el destructor. Y es precisamente aquí donde muchos principiantes quedan completamente desconcertados porque no entienden cómo funciona. Esto ocurre, sobre todo, porque muchos autores no explican las cosas como intento explicarlas aquí.
Para añadir el destructor al código 05 y ajustarlo plenamente a los principios de la programación orientada a objetos, basta con modificarlo como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. class C_Regression 07. { 08. private : 09. //+----------------+ 10. public : 11. //+----------------+ 12. C_Regression() 13. { 14. datetime dt0 = TimeCurrent(), 15. dt1[20]; 16. 17. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 18. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 19. ChartRedraw(); 20. } 21. //+----------------+ 22. ~C_Regression() 23. { 24. ObjectDelete(0, def_NameChannel); 25. ChartRedraw(); 26. } 27. //+----------------+ 28. }; 29. //+------------------------------------------------------------------+ 30. void OnStart(void) 31. { 32. C_Regression channel; 33. 34. Sleep(2000); 35. } 36. //+------------------------------------------------------------------+
Código 06
Ahora ejecuta el código 06 en el terminal de MetaTrader 5 y sorpréndete con el resultado, mi querido lector. Comprobarás que coincide con el de la animación 01. ¿Por qué? Recuerda que MetaTrader 5 solo ejecutará el código contenido en el procedimiento OnStart. Sin embargo, al observar este procedimiento, vemos que solo contiene dos líneas. Aun así, el código conserva su comportamiento: crea el objeto, lo coloca en el gráfico, deja transcurrir cierto tiempo y después lo elimina. Entonces, una vez más, la pregunta es: ¿cómo ha ocurrido? La respuesta es que se está llamando al destructor.
¿Cómo es eso? No veo absolutamente ninguna llamada aparte de las dos líneas del procedimiento OnStart. Lo único diferente que encuentro en el código 06 es que has creado otra función miembro especial dentro de la clase con el mismo nombre que esta, pero precedido por una tilde. Al observarlo con calma, noto que contiene el código que ocupaba las líneas 30 y 31 del código 05. No se ha añadido nada más. Sin embargo, afirmas —y puedo comprobarlo al ejecutar el código— que el objeto se elimina. Ahora sí que no entiendo nada. Esto no tiene el menor sentido para mí. Ya lo sé: esto solo puede ser cosa del DEMONIO. Porque DIOS no haría algo tan complicado.
En efecto, mi querido lector, los destructores tienen algo de curioso. Para comprenderlos correctamente, necesitamos conocer otro concepto. Ese concepto se explicó en los primeros artículos de esta serie, cuando hablamos de las variables. Presta atención y entenderás por qué funciona el código 06, además de cómo declarar y utilizar un destructor.
En el artículo Del básico al intermedio: Variables (I), hablamos de la vida útil y la visibilidad de las variables. Si tienes alguna duda al respecto, consulta ese artículo para obtener más detalles. Comprender ambos conceptos te ayudará a entender cuándo llama el compilador a un destructor y también te permitirá aprovechar mejor la programación orientada a objetos.
Muy bien, vayamos por partes, como diría Jack. Primero, al declarar un destructor, debes hacerlo como se muestra en la línea 22 del código 06. Es decir, debes añadir el símbolo (~) antes del nombre de la clase. De este modo, el compilador sabrá que la función miembro especial que estás declarando es el destructor de la propia clase. Como los destructores y los constructores NUNCA DEVUELVEN NINGÚN VALOR, no puedes utilizarlos con ese fin. Debes confiar en que tu código hará todo de la mejor manera posible.
Segundo punto: a diferencia de los constructores, que pueden recibir argumentos de inicialización, como vimos en el artículo anterior, los destructores NUNCA, JAMÁS y BAJO NINGUNA CIRCUNSTANCIA reciben argumentos. Intentarlo es un error que impedirá compilar el código y, por tanto, generar el ejecutable.
Tercero: los destructores siempre se llaman de forma implícita, salvo cuando utilizamos dos operadores que veremos dentro de poco. Es decir, en teoría, TÚ NO CONTROLAS cuándo ni dónde se llamará a un destructor. El compilador lo determina a partir del tiempo de vida de una variable. Insisto: esto es así en teoría. En la práctica, existen formas de controlar cuándo y dónde se llamará al destructor. Por ahora, nos centraremos en la parte más sencilla de comprender.
De acuerdo, pero ahora surge una pregunta: ¿qué demonios es eso del tiempo de vida de una variable? Pues bien, mi querido lector, hablamos de ello en el primer artículo de esta serie. Aun así, podemos repasarlo brevemente aquí.
Una variable nace en el momento de su declaración y muere al finalizar el bloque en el que fue declarada.
Muy bien, pero eso no me ha ayudado en absoluto, porque sigo sin entenderlo. En ese caso, te sugiero que vuelvas a los primeros artículos de esta serie y los estudies. Tener claros estos conceptos te permitirá aprovechar al máximo MQL5. Sin ellos, olvídalo: no conseguirás seguir la explicación.
Continuemos. Cuando declaramos en la línea 32 la variable channel, cuyo tipo es la clase C_Regression, se llama al constructor predeterminado de la clase. Esto se explicó en el artículo anterior. Sin embargo, cuando finaliza el bloque de código que comienza en la línea 31, es decir, al llegar a la línea 35, se destruyen las variables declaradas en él. En este caso, la única variable del bloque es channel. Como el compilador sabe que channel es una instancia de una clase, buscará el destructor dentro de C_Regression. Si no encuentra ninguno, proporcionará uno para que el objeto pueda destruirse correctamente. Recuerda lo siguiente: el compilador NO SABE CÓMO DESTRUIR EL OBJETO. Solo generará un destructor implícito conforme a las reglas del lenguaje. De este modo, podrá compilar el código y generar el ejecutable.
Sin embargo, como en nuestra clase C_Regression hemos definido e implementado un destructor en la línea 22, el compilador generará una llamada para ejecutar ese código cuando finalice el tiempo de vida del objeto asociado a la variable channel. Por esta razón, aunque el código no lo indique explícitamente, el objeto OBJ_REGRESSION se elimina del gráfico.
¡Qué interesante! Ahora sí empiezo a entender cómo funciona el código 06 y cómo se llama al destructor. Sin embargo, acaba de surgirme una inquietud. He seguido estos artículos y he aprendido muchas cosas interesantes de una forma muy sencilla. No obstante, lo que se ha mostrado aquí parece aplicarse únicamente a este caso concreto. ¿No podríamos hacerlo de otra manera? ¿A qué te refieres, mi querido lector? Intenta ser un poco más específico para que pueda ayudarte.
Quiero saber lo siguiente: en el código 06 utilizamos un script para demostrar algo que antes vimos con el indicador creado en el código 03. Hasta aquí, todo está claro. En el indicador, cuando pedíamos a MetaTrader 5 que lo retirara, se llamaba a la función OnDeinit. En el código 06, esto equivaldría a la llamada al destructor de la línea 22, como se explicó anteriormente. Lo mismo ocurre con el constructor. En el código 03, la función OnInit de la línea siete se encargaba de esa tarea. En el código 06, la realiza el constructor de la línea doce, que se ejecuta cuando se ejecuta la línea 32. Mi duda es la siguiente: ¿podríamos utilizar la misma clase del código 06 dentro del código 03? Y, en caso afirmativo, ¿cómo deberíamos hacerlo?
Excelente pregunta, mi querido lector. La respuesta es SÍ. Podemos utilizar la misma clase implementada en el código 06 dentro del código 03 sin ningún problema. Para que puedas entender claramente cómo hacerlo, trasladaremos la clase a un archivo de encabezado. Así podremos utilizarla tanto en el script como en cualquier otro código, ya sea un indicador, un Asesor Experto o incluso un servicio. Aunque, en el caso de los servicios, necesitaremos realizar algunos pasos adicionales que explicaremos en otro momento.
Con esto en mente, creamos el archivo de encabezado que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. class C_Regression 03. { 04. private : 05. //+----------------+ 06. public : 07. //+----------------+ 08. C_Regression() 09. { 10. datetime dt0 = TimeCurrent(), 11. dt1[20]; 12. 13. CopyTime(NULL, NULL, dt0, dt1.Size(), dt1); 14. ObjectCreate(0, def_NameChannel, OBJ_REGRESSION, 0, dt1[0], 0, dt0, 0); 15. ChartRedraw(); 16. } 17. //+----------------+ 18. ~C_Regression() 19. { 20. ObjectDelete(0, def_NameChannel); 21. ChartRedraw(); 22. } 23. //+----------------+ 24. }; 25. //+------------------------------------------------------------------+
Código 07
Observa que el código de la clase se ha mantenido sin modificaciones. Para utilizarlo en un script como el del código 06, procederemos de una forma ligeramente distinta. El nuevo código se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_NameChannel "Demo" 05. //+------------------------------------------------------------------+ 06. #include <Tutorial\File 01.mqh> 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. C_Regression channel; 11. 12. Sleep(2000); 13. } 14. //+------------------------------------------------------------------+
Código 08
Si no entiendes el código 08, te sugiero que consultes el artículo Del básico al intermedio: Directiva Include, donde se explica cómo funciona esta directiva y qué precauciones debemos tomar para que el código funcione correctamente. También debes comprender cómo funcionan las definiciones. Para ello, puedes leer el artículo Del básico al intermedio: Definiciones (I), que explica lo necesario para entender por completo el código 08.
De acuerdo, ahora tenemos un archivo de encabezado, mostrado en el código 07, que puede utilizarse en otros tipos de código MQL5. Veamos cómo usar esta misma clase, pero ahora en un código equivalente al código 03. Para ello, necesitamos crear algo parecido a lo que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. int OnInit() 10. { 11. return INIT_SUCCEEDED; 12. }; 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. return rates_total; 17. }; 18. //+------------------------------------------------------------------+ 19. void OnDeinit(const int reason) 20. { 21. }; 22. //+------------------------------------------------------------------+
Código 09
Para utilizar correctamente la clase en este código 09, debemos retomar el tema de las variables. Una vez más, sin comprender los conceptos más sencillos, no podrás entender otros más elaborados. Por eso, si tienes alguna duda, vuelve a estudiar los artículos anteriores. Nuestro problema aquí es el tiempo de vida de una variable. A veces, este concepto complica un poco las cosas.
En el código 03 no existía esta preocupación, porque no necesitábamos definir ninguna variable. Todo se hacía de forma local y completamente aislada. Sin embargo, en este nuevo código no podremos actuar de la misma manera. Si lo intentamos, la variable local se destruirá en cuanto finalice el bloque de código. Entonces, ¿cómo resolvemos esta cuestión? La solución consiste en utilizar una variable de ámbito global. De este modo, no se destruirá hasta que finalice el programa. Es aquí donde las cosas se vuelven mucho más interesantes y divertidas, pero también bastante más peligrosas para quien no presta atención a lo que está implementando o intenta saltarse etapas del aprendizaje. El código que produce el mismo resultado visto hasta ahora se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Demo" 04. //+------------------------------------------------------------------+ 05. #define def_NameChannel "Demo" 06. //+------------------------------------------------------------------+ 07. #include <Tutorial\File 01.mqh> 08. //+------------------------------------------------------------------+ 09. C_Regression gl_Channel; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. return INIT_SUCCEEDED; 14. }; 15. //+------------------------------------------------------------------+ 16. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 17. { 18. return rates_total; 19. }; 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. }; 24. //+------------------------------------------------------------------+
Código 10
El código 10 resulta bastante sorprendente si lo analizas con calma. En ningún momento creamos nada explícitamente. Solo necesitamos declarar en la línea nueve una variable cuyo tipo es C_Regression, definida en el archivo incluido mediante la línea siete. Quizá estés pensando: vaya, este código no hará absolutamente nada, porque dentro de la función OnInit no se ejecuta ninguna operación. Lo mismo ocurre con los demás manejadores de eventos, como puede comprobarse al analizar OnCalculate y OnDeinit. En resumen: este código es completamente inútil. Y punto.
Hum. Creo que no has entendido bien cómo gestiona el compilador el funcionamiento de las clases. Por eso afirmas que el código 10 es completamente inútil. Sin embargo, a diferencia del código 09, que realmente no hace nada, no podemos decir lo mismo del código 10. Este sí realiza una operación: coloca en el gráfico el objeto definido dentro de la clase C_Regression. Pero ¿por qué ocurre esto si aparentemente no existe ningún fragmento de código dentro de las funciones y procedimientos que ejecutará MetaTrader 5? Si realmente funciona, no consigo comprender el motivo. ¿Podrías explicármelo para que yo también pueda entenderlo?
Bien, mi querido lector, debes recordar que al declarar una variable estamos ejecutando una operación. Sin embargo, cuando trabajamos con clases, a diferencia de otros tipos de datos y estructuras de registro, se ejecuta una tarea algo más compleja. Como ya he explicado, cuando el compilador encuentra la declaración de una variable de tipo clase, busca un constructor adecuado. Si la clase no declara ninguno, genera uno que permita inicializar correctamente el objeto. Si tu código define un constructor adecuado, el compilador genera una llamada para ejecutar exactamente el código implementado en él.
Por tanto, cuando se ejecute la línea nueve, se llamará al constructor de la clase. Como este constructor se encuentra en la línea ocho del código 07, se ejecutará todo su contenido y se creará el objeto con el que hemos trabajado desde el comienzo del artículo. Este objeto permanecerá en el gráfico hasta que se retire definitivamente el indicador, que en este caso corresponde al código 10. Cuando eso ocurra, se destruirá la variable global declarada en la línea nueve del código 10. En ese preciso instante se llamará al destructor. Es decir, se ejecutará todo el código implementado en el destructor de la línea 18 del código 07.
Como dije al principio, en la programación orientada a objetos las cosas no siempre son lo que parecen. Por eso debes estudiar y practicar antes de asumir que ya dominas algo que todavía no comprendes realmente.
De acuerdo, mi amigo autor. Coincido contigo en este punto. Sin embargo, antes de terminar me gustaría preguntarte algo. Si el código 10 funciona como acabas de explicar, antes de terminar me gustaría saber algo: ¿existe alguna forma de controlar el momento en que se ejecutará el código de la clase? Mi intención es evitar un código extraño, en el que el simple hecho de declarar una variable de tipo clase ya provoque la ejecución del constructor. Entonces, ¿podemos controlar este comportamiento? Sí, mi querido lector. Pero, para no complicar aún más este artículo, no lo veremos ahora.
Consideraciones finales
Quizá este sea el artículo que deje a más personas sin saber muy bien qué hacer. Aunque he intentado explicar el tema de la forma más sencilla y didáctica posible, resulta prácticamente imposible comprender todo el contenido solo con leer el artículo. Necesitas estudiar y practicar lo que hemos visto aquí para entender realmente cómo funcionan estos mecanismos y por qué se comportan de esa manera.
Sé que este tema resulta bastante confuso al principio. Por eso dediqué mucho tiempo a estudiar y tratar de comprender C++. Aunque algunos creen que puede aprenderse en pocos días, lamento decir que eso no ocurre en la práctica. Dominar C++ requiere meses e incluso años, hasta que puedas decir: sí, soy programador de C++. Sin embargo, MQL5 no es C++. De hecho, MQL5 es muchísimo más sencillo que C++. Eso no significa que el conocimiento adquirido durante años de programación no me resulte útil. Muy al contrario. Gran parte de lo que explico y demuestro en este artículo procede de toda mi experiencia y de las dificultades que tuve para aprender programación orientada a objetos.
Por eso, mi querido lector, aférrate bien a los conocimientos que estoy compartiendo y empieza a practicar y estudiar lo que se muestra en esta breve serie de artículos. A partir del próximo artículo, las cosas comenzarán a complicarse un poco si no has comprendido correctamente lo que hemos visto y explicado aquí. Nos vemos, entonces, en el próximo artículo, donde explicaré cómo controlar mejor el uso de las clases para evitar lo que ocurre en el código 10.
| Archivo MQ5 | Descripción |
|---|---|
| Indicador\Code 01 | Archivo de demostración |
| Indicador\Code 02 | Archivo de demostración |
| Script\Code 01 | Archivo de demostración |
| Script\Code 02 | Archivo de demostración |
| Script\Code 03 | Archivo de demostración |
| Script\Code 04 | Archivo de demostración |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16764
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Aprendizaje automático y Data Science (Parte 44): Pronóstico de series temporales OHLC de Forex mediante autorregresión vectorial (VAR)
Particularidades del trabajo con números del tipo double en MQL4
De novato a experto: Períodos del mercado Forex
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso