The Order of Object Creation and Destruction in MQL5

MetaQuotes | 1 March, 2010

What is this article about?

MQL5-programs are written in concepts of Object Oriented Programming (OOP), and that not only opens up new possibilities for creating custom libraries, but also allows you to use complete and tested classes of other developers. In the Standard Library, that is included into the MetaTrader 5 Client Terminal, there are hundreds of classes that contain thousands of methods.

To take full advantages of OOP we must clarify some details about creating and deleting objects in MQL5 programs. Creating and Deleting Objects is briefly described in Documentation, and this article will illustrate this topic in examples.

Initialization and Deinitialization of Global Variables

Initialization of global variables is done right after MQL5-program start and before any function call. During initialization initial values are assigned to the variables of simple types, and the constructor for objects is called, if it is declared in them. 

As an example, let's declare two classes CObjectA and CObjectB. Every class has a constructor and destructor, containing simple Print() function. Let's declare the variables of that class types globally and run the 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__);
  }

The script result is shown in the Experts journal:

GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectA::ObjectA  Constructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectB::ObjectB  Constructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    OnStart
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectB::~ObjectB  Destructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectA::~ObjectA  Destructor

From the journal it is clear that the order of initialization match the order of declaration of variables in GlobalVar_TestScript.mq5 script, and deinitialization is done in reverse order before MQL5-program roll-out.

Initialization and Deinitialization of Local Variables

Local variables are deinitialized at the end of the program block, in which they were declared, and in reverse order of their declaration. The program block is a compound operator that may be the part of switch operator, loop operators (for, while and do-while), body of function or part of if-else operator.

Local variables are initialized only if they are used in program. If a variable is declared, but the block of code in which it is declared is not executed, then this variable is not created and hence it is not initialized. 

To illustrate this let's go back to our CObjectA and CObjectB classes, and create the new class CObjectС. Classes are still declared globally, but the variables of these classes are now declared locally in the OnStart() function.

Let's declare the variable of CObjectA class explicitly in the first line of function, but objects of CObjectB and CObjectС classes will be declared in separate blocks, that will be executed depending on the value of execute input variable. In MetaEditor input variables of MQL5-programs are highlighted with brown.

//+------------------------------------------------------------------+
//|                                          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;
     }
  }
//+------------------------------------------------------------------+

The result is:

LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectA::CObjectA  Constructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectC::CObjectC  Constructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectC::~CObjectC  Destructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectA::~CObjectA  Destructor

The object of CObjectA class will always be initialized first automatically, no matter of what value has the execute input parameter. Then either object objB or object objC is automatically initialized - it depends on what block is executed according to the value of execute input parameter. By default, this parameter has false value, and in this case after initialization of objA variable comes the initialization of objC variable. This is obvious in constructor and destructor execution.

But whatever the order of initialization is (regardless of execute parameter), the deinitialization of complex type variables is done in reverse order of their initialization. This applies both to local and to global class objects created automatically. In this case there is no difference between them.

Initialization and Deinitialization of Dynamically Created Objects

In MQL5 compound objects are initialized automatically, but if you want to manually control the process of objects creation you must use object pointers. A variable declared as an object pointer of some class, doesn't contain the object itself, and there is no automatic initialization of that object.

Pointers can be declared locally and/or globally, and at the same time they can be initialized with empty value NULL of inherited type. Object creation is done only, when new operator is applied to object pointer, and it doesn't depend on object pointer declaration.

Dynamically created objects are deleted using delete operator, so we must handle it. As an example let's declare globally two variables: one of CObjectA type and one of CObjectB type, and another variable of CObjectC type with object pointer.

//+------------------------------------------------------------------+
//|                                       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);
  }
//+------------------------------------------------------------------+

Despite the fact, that dynamically created object pointer pObjectC is declared before the static variables first and second, this very object is initialized only when it is created by new operator. In this example the new operator is in the OnStart() function.

GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectA::CObjectA  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectB::CObjectB  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectC::CObjectC  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    OnStart
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectC::~CObjectC  Destructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectB::~CObjectB  Destructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectA::~CObjectA  Destructor

When the program execution in the OnStart() function reaches the operator

   pObjectC=new CObjectC;

the object is initialized and the constructor for this object is called. Then the program executes this string

   Print(__FUNCTION__);

which outputs the following text in the Journal:

GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    OnStart

and then dynamically created object is deleted by calling the delete operator:

   delete(pObjectC);

So, objects are initialized dynamically during their creation by new operator and are deleted by delete operator. 

Mandatory requirement: all objects created using expression object_pointer=new Class_Name, must always be deleted using delete(object_pointer) operator. If for some reason dynamically created object (after the end of block where it was initialized) was not deleted using delete operator, a corresponding message will be shown in Experts journal.


Deleting Dynamically Created Objects

As it has been mentioned earlier, every dynamically created object is initialized using new operator and must always be deleted using delete operator. But don't forget that the new operator creates an object and returns a pointer to that object.  Created object itself is not in the variable, containing the object pointer. You may declare several pointers and assign them to the same object pointer.

//+------------------------------------------------------------------+
//|                                        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.
  }
//+------------------------------------------------------------------+

The output says that there are several undeleted objects left. But there will be only 5 undeleted objects instead of 10, as you may think, because the new operator has created only 5 objects.

(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    5 undeleted objects left

Even if the destructor for dynamically created object is not called (the object is not deleted using delete operator), the memory will still be cleared. But in "Experts" journal it is said that object was not deleted. This can help you to find out the improper object management and to fix the error.

In the next example let's try to delete the pointers in each of two pointer arrays array1 and array2.

//+------------------------------------------------------------------+
//|                                        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
  }
//+------------------------------------------------------------------+

The result in Experts tab is now different.

(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer

The CItem created objects were successfully deleted in the first for() loop, but further attempts to delete objects that don't exist, in the second loop caused some messages about invalid pointers. Dynamically created object must be deleted once, and before the use of any object pointer it must be checked with CheckPointer() function.

Pointer checking using CheckPointer() function

CheckPointer() is used to check pointers and allows to identify the pointer type. When you are working with dynamically created objects there are two possible: 

  • undeleting at the end of execution block
  • attempt to delete already deleted object 

Let's take another example, that illustrates objects interrelation. Let's create two classes: the first class CItemArray contains the pointer array of another class 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");
     }
  }

The classes themselves do not contain any error, but their use may bring surprises. The first variant of the script:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItemArray items_array;
   CItem array[5];
   items_array.SetArray(array);
  }

Running this variant of script will display the following messages:

(GBPUSD,H1)    16:06:17    CItemArray::CItemArray  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItemArray::~CItemArray  Destructor
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete

As the declaration of CItemArray class variable comes first, it is initialized first and class destructor is called. Then array[5] is declared, containing CItem class object pointers. That's why we see five messages about initialization of each object.

In the last line of this simple script the pointers from array[5] array are copied to internal object pointers array named items_array (See 'LocalVar_TestScript_4.mq5').

   items_array.SetArray(array);

For now the script stops executing, and automatically created objects are automatically deleted. The first object to be deleted is one, that was initialized last - it is array[5] pointers array. Five Journal records about calling the CItem class destructor confirm this. Then comes the message about calling the destructor for items_array object, as it was initialized right before array[5] variable. 

But CArrayItem class destructor calls the protected Destroy() function, which tries to delete CItem objects through pointers in m_array[] via delete operator. The pointer is checked first and if it is invalid, the objects are not deleted, and "Invalid pointer to delete" message is displayed. 

There are 5 such records in Journal, i.e. all pointers in m_array[] array are invalid. That happened because the objects of that pointers have been already deinitialized during array[] array deinitialization.

Let's tweak our script, swapping the declarations of items_array and items_array[] variables.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItem array[5];
   CItemArray items_array;
   items_array.SetArray(array);
  }

The corrected script doesn't produce errors. First the items_array variable was deinitialized, as it was declared last. During its deinitialization the ~CItemArray() class destructor was called, that, in its turn, called the Destroy() function. 

In this order of declaration the items_array is deleted before the array[5] array. In the Destroy() function, that is called from items_array destructor, pointer objects still exist, so no errors occur.

The correct deleting of dynamically created objects can also be seen in example of GetPointer() function. In this example the Destroy() function is explicitly called to ensure the proper order of objects deleting.

Conclusion

As you can see, objects creating and deleting is done simply. Just review all the examples from this article, and you can make your own variants of interrelations between automatically and dynamically created objects. 

You should always check your classes for correct deletion of objects and properly design your destructors, so there are no errors when accessing invalid pointers. Remember, that if you use objects that are dynamically created using new operator, you must correctly delete these objects using delete operator.

From this article you have learnt only the order of objects creation and deletion in MQL5. Organizing a secure work with object pointers is beyond the scope of this article.