
L'ordre de création et de destruction d'objets dans MQL5
De quoi parle l'article ?
Les programmes MQL5 sont écrits dans des concepts de programmation orientée objet (POO), et cela ouvre non seulement de nouvelles possibilités pour créer des bibliothèques personnalisées, mais vous permet également d'utiliser des classes complètes et testées d'autres développeurs. Dans la bibliothèque standard, qui est incluse dans le terminal client MetaTrader 5, il existe des centaines de classes qui contiennent des milliers de méthodes.
Pour tirer pleinement parti de la POO, nous devons clarifier certains détails sur la création et la suppression d'objets dans les programmes MQL5. La création et la suppression d'objets sont brièvement décrites dans Documentation, et cet article illustrera ce sujet dans des exemples. Initialisation et désinitialisation des variables globales
L’initialisation des variables globales se fait juste après le démarrage du programme MQL5 et avant tout appel de fonction. Lors de l'initialisation, des valeurs initiales sont assignées aux variables de types simples, et le constructeur des objets est appelé, s'il y est déclaré.
A titre d'exemple, déclarons deux classes CObjectA et CObjectB. Chaque classe a un constructeur et un destructeur, contenant une simple fonction Print(). Déclarons les variables de ces types de classe globalement et exécutons le script.
//+------------------------------------------------------------------+ //| GlobalVar_TestScript.mq5 | //| http://www.mql5.com | //+------------------------------------------------------------------+ class CObjectA { public: CObjectA(){Print(__FUNCTION__," Constructor");} ~CObjectA(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectB { public: CObjectB(){Print(__FUNCTION__," Constructor");} ~CObjectB(){Print(__FUNCTION__," Destructor");} }; //--- declaring the objects globally CObjectA first; CObjectB second; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Print(__FUNCTION__); }
Le résultat du script est affiché dans le journal Experts :
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectA::ObjectA Constructeur
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectB::ObjectB Constructeur
GlobalVar_TestScript (EURUSD,H1) 13:05:07 OnStart
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectB::~ObjectB Destructeur
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectA::~ObjectA Destructeur
D'après le journal, il est clair que l'ordre d'initialisation correspond à l'ordre de déclaration des variables dans le script GlobalVar_TestScript.mq5 et que la désinitialisation est effectuée dans l'ordre inverse avant le déploiement du programme MQL5.
Initialisation et désinitialisation des variables locales
Les variables locales sont désinitialisées à la fin du bloc de programme dans lequel elles ont été déclarées et dans l'ordre inverse de leur déclaration. Le bloc de programme est un opérateur composé qui peut faire partie de l'opérateur switch, des opérateurs de boucle (for, while et do-while), du corps de la fonction ou une partie de l’ opérateur if-else.
Les variables locales ne sont initialisées que si elles sont utilisées dans le programme. Si une variable est déclarée, mais que le bloc de code dans lequel elle est déclarée n'est pas exécuté, alors cette variable n'est pas créée et donc elle n'est pas initialisée.
Pour illustrer cela, revenons à nos CObjectA and CObjectB classes, et créons la nouvelle classe CObjectС. Les classes sont toujours déclarées globalement, mais les variables de ces classes sont désormais déclarées localement dans la fonction OnStart().
Déclarons la variable de la CObjectA classclasse explicitement dans la première ligne de fonction, mais les objets des classes CObjectB et CObjectС seront déclarés dans des blocs séparés, qui seront exécutés en fonction de la valeur de la variable d’entrée execute. Dans MetaEditor, les variables d'entrée des programmes MQL5 sont surlignées en marron.
//+------------------------------------------------------------------+ //| LocalVar_TestScript.mq5 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property script_show_inputs //--- input parameters input bool execute=false; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectA { public: CObjectA(){Print(__FUNCTION__," Constructor");} ~CObjectA(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectB { public: CObjectB(){Print(__FUNCTION__," Constructor");} ~CObjectB(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectC { public: CObjectC(){Print(__FUNCTION__," Constructor");} ~CObjectC(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CObjectA objA; //--- this block will NOT be executed if execute==false if(execute) { CObjectB objB; } //--- this block WILL be executed if execute==false if(!execute) { CObjectC objC; } } //+------------------------------------------------------------------+
Le résultat est :
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectA::CObjectA Constructeur
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectC::CObjectC Constructeur
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectC::~CObjectC Destructeur
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectA::~CObjectA Destructeur
L'objet de la classe CObjectA sera toujours initialisé en premier automatiquement, quelle que soit la valeur du paramètre d'entrée d'exécution. Ensuite, l'objet objB ou l’ objet objC est automatiquement initialisé - cela dépend du bloc exécuté en fonction de la valeur du paramètre d'entrée d'exécution. Par défaut, ce paramètre a une valeur false, et dans ce cas après l'initialisation de la variable objA vient l'initialisation de la variable objC. Ceci est évident dans l'exécution du constructeur et du destructeur.
Mais quel que soit l'ordre d'initialisation (quel que soit le paramètre d'exécution), la désinitialisation des variables de type complexe se fait dans l'ordre inverse de leur initialisation. Cela s'applique à la fois aux objets locaux et aux objets de classe globaux créés automatiquement. Dans ce cas, il n'y a pas de différence entre eux.
Initialisation et désinitialisation des objets créés dynamiquement
Dans MQL5, les objets composés sont initialisés automatiquement, mais si vous souhaitez contrôler manuellement le processus de création d'objets, vous devez utiliser des pointeurs d’objet. Une variable déclarée en tant que pointeur d'objet d'une classe ne contient pas l'objet lui-même et il n'y a pas d'initialisation automatique de cet objet.
Les pointeurs peuvent être déclarés localement et/ou globalement, et en même temps ils peuvent être initialisés avec une valeur vide NULLE de type hérité. La création d'objet est effectuée uniquement lorsque l'opérateur new est appliqué au pointeur d'objet et ne dépend pas de la déclaration du pointeur d'objet.
Les objets créés dynamiquement sont supprimés à l'aide de l'opérateur delete, nous devons donc le gérer. A titre d'exemple, déclarons globalement deux variables : une de type CObjectA et une de type CObjectB, et une autre variable de type CObjetC avec pointeur d'objet.
//+------------------------------------------------------------------+ //| GlobalVar_TestScript_2.mq5 | //| http://www.mql5.com | //+------------------------------------------------------------------+ class CObjectA { public: CObjectA(){Print(__FUNCTION__," Constructor");} ~CObjectA(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectB { public: CObjectB(){Print(__FUNCTION__," Constructor");} ~CObjectB(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CObjectC { public: CObjectC(){Print(__FUNCTION__," Constructor");} ~CObjectC(){Print(__FUNCTION__," Destructor");} }; CObjectC *pObjectC; CObjectA first; CObjectB second; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- pObjectC=new CObjectC; Print(__FUNCTION__); delete(pObjectC); } //+------------------------------------------------------------------+
Malgré le fait que le pointeur d'objet créé dynamiquement pObjectC soit déclaré avant les variables statiques premier et second, cet objet même n'est initialisé que lorsqu'il est créé par l'opérateur new. Dans cet exemple, l'opérateur new se trouve dans la fonction OnStart().
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectA::CObjectA Constructeur
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectB::CObjectB Constructeur
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectC::CObjectC Constructeur
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 OnStart
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectC::~CObjectC Destructeur
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectB::~CObjectB Destructeur
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectA::~CObjectA Destructeur
Lorsque l'exécution du programme dans la fonction OnStart() atteint l'opérateur
pObjectC=new CObjectC;
l'objet est initialisé et le constructeur de cet objet est appelé. Ensuite, le programme exécute cette chaîne
Print(__FUNCTION__);
qui génère le texte suivant dans le Journal :
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 OnStart
puis l'objet créé dynamiquement est supprimé en appelant l'opérateur delete :
delete(pObjectC);
Ainsi, les objets sont initialisés dynamiquement lors de leur création par l'opérateur new et sont supprimés par l'opérateur.delete.
Exigence obligatoire : tous les objets créés à l'aide de l'expression object_pointer=new Class_Name doivent toujours être supprimés à l'aide de l'opérateur delete(object_pointer). Si, pour une raison quelconque, l'objet créé dynamiquement (après la fin du bloc où il a été initialisé) n'a pas été supprimé à l'aide de l'opérateur delete, un message correspondant sera affiché dans le journal des experts.
Suppression d’objets créés dynamiquement
Comme il a été mentionné précédemment, chaque objet créé dynamiquement est initialisé à l'aide de l'opérateur new et doit toujours être supprimé à l'aide de l'opérateur delete. Mais n'oubliez pas que l'opérateur new crée un objet et renvoie un pointeur vers cet objet. L'objet créé n’es lui-même pas dans la variable, contenant le pointeur d'objet. Vous pouvez déclarer plusieurs pointeurs et les assigner au même pointeur d'objet.
//+------------------------------------------------------------------+ //| LocalVar_TestScript_1.mq5 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property link "http://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| simple class | //+------------------------------------------------------------------+ class CItem { public: CItem(){Print(__FUNCTION__," Constructor");} ~CItem(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- declaring the first object pointer array CItem* array1[5]; //--- declaring the first object pointer array CItem* array2[5]; //--- filling arrays in the loop for(int i=0;i<5;i++) { //--- creating a pointer for the first array using new operator array1[i]=new CItem; //--- creating a pointer for the second array via copy from the first array array2[i]=array1[i]; } // We "forgot" to delete objects before exiting the function. See "Experts" tab. } //+------------------------------------------------------------------+
La sortie indique qu'il reste plusieurs objets non supprimés. Mais il n'y aura que 5 objets non supprimés au lieu de 10, comme vous pouvez le penser, car l’opérateur new n'a créé que 5 objets.
(GBPUSD,H1) 12:14:04 CItem::CItem Constructeur
(GBPUSD,H1) 12:14:04 CItem::CItem Constructeur
(GBPUSD,H1) 12:14:04 CItem::CItem Constructeur
(GBPUSD,H1) 12:14:04 CItem::CItem Constructeur
(GBPUSD,H1) 12:14:04 CItem::CItem Constructeur
(GBPUSD,H1) 12:14:04 5 objets non supprimés restants
Même si le destructeur de l'objet créé dynamiquement n'est pas appelé (l'objet n'est pas supprimé à l'aide de l'opérateur delete), la mémoire sera toujours effacée. Mais dans le journal « Experts », il est dit que l'objet n'a pas été supprimé. Cela peut vous aider à découvrir la gestion incorrecte des objets et à corriger l'erreur.
Dans l'exemple suivant, essayons de supprimer les pointeurs dans chacun des deux tableaux de pointeurs – tableau1 et tableau2.
//+------------------------------------------------------------------+ //| LocalVar_TestScript_2.mq5 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property link "http://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| simple class | //+------------------------------------------------------------------+ class CItem { public: CItem(){Print(__FUNCTION__," Constructor");} ~CItem(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- declaring the first object pointer array CItem* array1[5]; //--- declaring the second object pointer array CItem* array2[5]; //--- filling arrays in the loop for(int i=0;i<5;i++) { //--- creating a pointer for the first array using new operator array1[i]=new CItem; //--- creating a pointer for the second array via copy from the first array array2[i]=array1[i]; } //--- deleting object using pointers of second array for(int i=0;i<5;i++) delete(array2[i]); //--- let's try to delete objects using pointers of first array for(int i=0;i<5;i++) delete(array2[i]); // in Experts tab there are messages about trying to delete invalid pointer } //+------------------------------------------------------------------+
Le résultat dans l'onglet Experts est maintenant différent.
(GBPUSD,H1) 15:02:48 CItem::CItem Constructeur
(GBPUSD,H1) 15:02:48 CItem::CItem Constructeur
(GBPUSD,H1) 15:02:48 CItem::CItem Constructeur
(GBPUSD,H1) 15:02:48 CItem::CItem Constructeur
(GBPUSD,H1) 15:02:48 CItem::CItem Constructeur
(GBPUSD,H1) 15:02:48 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 15:02:48 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 15:02:48 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 15:02:48 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 15:02:48 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 15:02:48 supprimer le pointeur non valide
(GBPUSD,H1) 15:02:48 supprimer le pointeur non valide
(GBPUSD,H1) 15:02:48 supprimer le pointeur non valide
(GBPUSD,H1) 15:02:48 supprimer le pointeur non valide
(GBPUSD,H1) 15:02:48 supprimer le pointeur non valide
Les objets créés par CItem ont été supprimés avec succès dans la première boucle for(), mais d'autres tentatives de suppression d'objets qui n'existent pas, dans la deuxième boucle ont provoqué des messages sur des pointeurs non valides. L'objet créé dynamiquement doit être supprimé une fois, et avant l'utilisation de tout pointeur d'objet, il doit être vérifié avec la fonction CheckPointer().
Vérification du pointeur à l'aide de la fonction CheckPointer()
CheckPointer() est utilisé pour vérifier les pointeurs et permet d'identifier le type de pointeur. Lorsque vous travaillez avec des objets créés dynamiquement, il y a deux possibilités :
- restauration à la fin du bloc d'exécution
- tentative de suppression d'un objet déjà supprimé
Prenons un autre exemple, qui illustre l'interrelation des objets. Créons deux classes : la première classe CItemArray contient le tableau de pointeurs d'une autre classe CItem.
//+------------------------------------------------------------------+ //| LocalVar_TestScript_3.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property link "http://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| simple class | //+------------------------------------------------------------------+ class CItem { public: CItem(){Print(__FUNCTION__," Constructor");} ~CItem(){Print(__FUNCTION__," Destructor");} }; //+------------------------------------------------------------------+ //| class, containing pointer array of CItem class | //+------------------------------------------------------------------+ class CItemArray { private: CItem *m_array[]; public: CItemArray(){Print(__FUNCTION__," Constructor");} ~CItemArray(){Print(__FUNCTION__," Destructor");Destroy();} void SetArray(CItem &array[]); protected: void Destroy(); }; //+------------------------------------------------------------------+ //| filling pointers array | //+------------------------------------------------------------------+ CItemArray::SetArray(CItem &array[]) { int size=ArraySize(array); ArrayResize(m_array,size); for(int i=0;i<size;i++)m_array[i]=GetPointer(array[i]); } //+------------------------------------------------------------------+ //| releasing | //+------------------------------------------------------------------+ CItemArray::Destroy(void) { for(int i=0;i<ArraySize(m_array);i++) { if(CheckPointer(m_array[i])!=POINTER_INVALID) { if(CheckPointer(m_array[i])==POINTER_DYNAMIC) delete(m_array[i]); } else Print("Invalid pointer to delete"); } }
Les classes elles-mêmes ne contiennent aucune erreur, mais leur utilisation peut apporter des surprises. La première variante du script :
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CItemArray items_array; CItem array[5]; items_array.SetArray(array); }
L'exécution de cette variante de script affichera les messages suivants :
(GBPUSD,H1) 16:06:17 CItemArray::CItemArray Constructeur
(GBPUSD,H1) 16:06:17 CItem::CItem Constructeur
(GBPUSD,H1) 16:06:17 CItem::CItem Constructeur
(GBPUSD,H1) 16:06:17 CItem::CItem Constructeur
(GBPUSD,H1) 16:06:17 CItem::CItem Constructeur
(GBPUSD,H1) 16:06:17 CItem::CItem Constructeur
(GBPUSD,H1) 16:06:17 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 16:06:17 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 16:06:17 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 16:06:17 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 16:06:17 CItem :: ~ CItem Destructeur
(GBPUSD,H1) 16:06:17 CItemArray::~CItemArray Destructeur
(GBPUSD,H1) 16:06:17 Pointeur à supprimer non valide
(GBPUSD,H1) 16:06:17 Pointeur à supprimer non valide
(GBPUSD,H1) 16:06:17 Pointeur à supprimer non valide
(GBPUSD,H1) 16:06:17 Pointeur à supprimer non valide
Comme la déclaration de la variable de classe CItemArray vient en premier, elle est initialisée en premier et le destructeur de classe est appelé. Ensuite, tableau[5] est déclaré, contenant des pointeurs d'objet de CItem class. C'est pourquoi nous voyons cinq messages concernant l'initialisation de chaque objet.
Dans la dernière ligne de ce script simple, les pointeurs du tableau array[5] sont copiés dans le tableau de pointeurs d'objets internes nommé items_array (voir « LocalVar_TestScript_4.mq5 »).
items_array.SetArray(array);
Pour l'instant, le script arrête de s'exécuter et les objets créés automatiquement sont automatiquement supprimés. Le premier objet à supprimer est celui qui a été initialisé en dernier - c’est array [5], le tableau des pointeurs. Cinq dossiers du Journal concernant l'appel du destructeur de la classe CItem le confirment. Vient ensuite le message concernant l'appel du destructeur pour l'objet items_array, car il a été initialisé juste avant la variable array[5].
Mais le destructeur de classe CArrayItem appelle la fonction protégée Destroy(), qui essaie de supprimer les objets CItem via des pointeurs dans m_array[] via l'opérateur delete. Le pointeur est vérifié en premier et s'il n'est pas valide, les objets ne sont pas supprimés et le message « pointeur non valide à supprimer » s’affiche
Il y a 5 dossiers de ce type dans le Journal, c'est-à-dire que tous les pointeurs du tableau m_array[] ne sont pas valides. Cela s'est produit parce que les objets de ces pointeurs ont déjà été désinitialisés lors de la désinitialisation array[] du tableau.
Ajustons notre script, en échangeant les déclarations des variables items_array and items_array[].
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CItem array[5]; CItemArray items_array; items_array.SetArray(array); }
Le script corrigé ne produit pas d'erreurs. Tout d'abord, la variable items_array a été désinitialisée, car elle a été déclarée en dernier. Lors de sa désinitialisation, le destructeur de classe ~CItemArray() a été appelé, qui, à son tour, a appelé la fonction Destroy().
Dans cet ordre de déclaration, le tableau items_array est supprimé avant le tableau array[5]. Dans la fonction Destroy(), qui est appelée à partir du destructeur items_array , les objets pointeurs existent toujours, donc aucune erreur ne se produit.
La suppression correcte des objets créés dynamiquement peut également être vue dans l'exemple de la fonction GetPointer(). Dans cet exemple, la fonction Destroy() est explicitement appelée pour garantir le bon ordre de la suppression d’objets.
Conclusion
Comme vous pouvez le voir, la création et la suppression d'objets se font simplement. Il suffit de passer en revue tous les exemples de cet article et vous pouvez créer vos propres variantes d'interrelations entre les objets créés automatiquement et dynamiquement.
Vous devez toujours vérifier dans vos classes la suppression correcte des objets et concevoir correctement vos destructeurs, afin qu'il n'y ait pas d'erreurs lors de l'accès à des pointeurs non valides. N'oubliez pas que si vous utilisez des objets créés dynamiquement à l'aide de l'opérateur new, vous devez correctement supprimer ces objets à l'aide de l'opérateur delete.
De cet article, vous n'avez appris que l'ordre de création et de suppression des objets dans MQL5. L'organisation d'un travail sécurisé avec des pointeurs d'objet dépasse le cadre de cet article.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/28





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation