Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
The Basics of Object-Oriented Programming

The Basics of Object-Oriented Programming

MetaTrader 5Examples | 7 December 2011, 12:43
25 200 13
Dmitry Fedoseev
Dmitry Fedoseev

 

Introduction

We can assume that anyone who tried to start learning object-oriented programming (OOP), first encountered such words as polymorphism, encapsulation, overload and inheritance. Maybe someone looked at some ready-made classes and tried to figure out where those polymorphism or encapsulation actually are... Most likely this can be the end of OOP learning process.

In fact, everything is much simpler as it seems. To use OOP you don't need to know what these words mean - you can just use the OOP features, not even knowing what they are called. Still I hope that everyone who will read this article will not only learn how to use OOP, but will also make clear of the meanings of these words.

 

Creating Libraries of Functions

The first and the most simple application of OOP is creating your own libraries of frequently used functions. Of course, you can simply store these functions in an include file (mqh). When you well need a function, simply include a file and call this function. However, if you program long enough you can can collect a huge amount of functions, so that it would be difficult to remember their names and purpose.

You can collect functions in different files, splitting them into categories based on purpose. For example, functions of working with arrays, functions of working with string, functions of calculating orders, etc. In the last sentence the word "category" can be replaced by the word "classes". The meaning remains the same, but we will come closer to the topic - Object-Oriented Programming.

So, the functions can be divided into classes: class of functions to work with arrays, class of functions to work with strings, class of functions to count orders, etc. The word "class" brings us closer to the subject of OOP as it is its fundamental concept. You can search various reference books, dictionaries and encyclopedias (for example Wikipedia) for what the "class in programming" is.

In object-oriented programming, a class is a construct that is used as a blueprint to create instances of itself.

Perhaps, the first impression would be about the same as that from the words "polymorphism", "encapsulation", etc. For this moment by the name of 'class' we will mean a set of functions and variables. In the case of using class to create a library - a set of functions and variables grouped by type of processed data or by type of processed objects: arrays, strings, orders.

 

A Program in Program

There were (and will be) lots of similar questions of the Forum - how to call a script from an Expert Advisor? While staying away of using third-party tools, this task is accomplished by placing the script code into the Expert Advisor code. In fact, it is not a difficult task, but a script can use the same names of variables and functions as EA, so you will need to adjust the script code. The changes are not complicated, but probably significant in volume.

It would be great to simply call this script as a separate independent program! This is possible if you will program the script as a class and then use this class. The amount of work will be increased only by a few lines of code. In this case, a class will combine functions not by the type of processed data, but according to their purpose. For example: a class to delete pending orders, a class to open position or to place order, a class to work with graphical objects, etc.

An important feature of class is that it is distinguished from the space it is located in. The class is like a program running in an operating system: multiple programs can run simultaneously, but on their own, independently of each other. Therefore, class can be called "a program in program", as it is distinguished from space it is located in.

 

Look and Feel of a Class

Class creation begins with the word class, followed by class name and then the entire code of class is placed in braces:

class CName 
  {
   // Here is the entire code of the class
  };
Attention! Do not forget to put a semicolon after the closing brace.

 

Visible and Hidden (Encapsulation)

If you take any program, we know that it includes a variety of functions. These functions can be divided into two types: main and auxiliary. The main functions are functions that a program is actually composed of. These functions may require many other functions that user does not need to know about. For example, in client terminal to open a position trader needs to open the New Order dialog, enter volume, values ​​of Stop Loss and Take Profit and then click "Buy" or "Sell".

But what is really going on between clicking on button and opening a position - only terminal developers can tell for sure. We can assume that terminal makes a lot of actions: checks the volume position, checks the values ​​of Stop Loss and Take Profit, checks network connection, etc. Lots and lots of procedures are hidden or, in other words, encapsulated. Similarly in a class you can split code into pieces (functions and variables) - some of them will be available when using a class, and some of them will be hidden.

Levels of encapsulation are defined using the following keywords: private, protected and public. Difference between protected and private will be considered a little later, but first we will touch upon private and public keywords. So, a simple class template takes the following form:

class CName 
  {
private:
   // Variables and functions available only inside the class
public:
   // Variables and functions available outside the class
  };
This is sufficient enough to take advantage of OOP. Instead of writing your code directly in the Expert Advisor (Script or Indicator), first create a class and then write everything in this class. Next we will consider the difference between the private and public sections on a practical example.

 

Example of Creating a Library

The class template presented above can be used to create a library of functions. Let's create a class to work with arrays. The most common tasks that may arise when using an array - are adding a new element to the array and adding a new element, provided that the element with given value does not exist in the array.

Lets name the function that adds an element to array as AddToEnd(), and the function that adds a unique element to array as AddToEndIfNotExists(). In the AddToEndIfNotExists() function first we will need to check if added element already exists in array, and if not - use the AddToEnd() function. Function, that checks if an element already exists in array, will be considered as auxiliary, therefore we will place it in the private section and all other functions - in the public section. As a result, we will get the following class:

class CLibArray 
  {
private:
   // Check if an element with required value exists in array
   int Find(int &aArray[],int aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1);  // No such element, return -1
     }
public:
   // Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
 

Loading Class

In order to use a class, it must be loaded. If a class is located in a separate file, you have to include this file

#include <OOP_CLibArray_1.mqh>

and then load this class. Class loading is similar to variable declaration:

CLibArray ar;

First comes the name of class, then the name of a pointer to refer to this instance. After loading the class becomes an object. In order to use any function of an object write the pointer name, the dot and then the function name. After you type the dot a drop down list of class functions will open (Figure 1).

Figure 1. List of Functions
Figure 1. List of Functions

Thanks to the drop down list there is no need to remember names of functions - you can navigate the list of names and remember the purpose of the function. This is the biggest advantage of using classes to create libraries as opposed to simply collecting functions in files.

In the case of collecting function, when you type a few initial letters of function name the drop down list will show all the functions from all included libraries, and when you use classes - only functions related to the specified class. Also note that the Find() function is not listed - this is the difference between the private and public sections. The function is written in the private section and therefore is not available.

 

Making a Universal Library for Different Data Types (Overload)

At this point, our library includes functions that work only with arrays of the int type. In addition to the int type arrays we may need to apply library functions to arrays of the following types: uint, long, ulong etc. For arrays of other data types we have to write their own functions. However, you don't need to give these functions other names - the correct function will is selected automatically depending on the type of passed parameter or set of parameters (in this example, depending on the type of parameters). Let's complement the class with functions of working with arrays of the long type:

class CLibArray 
  {
private:
   // Для int. Check if an element with required value exists in array
   int Find(int &aArray[],int aValue)
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
   // For long. Check if an element with required value exists in array
   int Find(long &aArray[],long aValue) 
     {
      for(int i=0; i<ArraySize(aArray); i++) 
        {
         if(aArray[i]==aValue) 
           {
            return(i); // Element exists, return index of element
           }
        }
      return(-1); // No such element, return -1
     }
public:
   // For int. Add to end of array
   void AddToEnd(int &aArray[],int aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For long. Add to end of array
   void AddToEnd(long &aArray[],long aValue) 
     {
      int m_size=ArraySize(aArray);
      ArrayResize(aArray,m_size+1);
      aArray[m_size]=aValue;
     }
   // For int. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(int &aArray[],int aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
   // For long. Add to end of array if there is no such value in array already
   void AddToEndIfNotExistss(long &aArray[],long aValue) 
     {
      if(Find(aArray,aValue)==-1) 
        {
         AddToEnd(aArray,aValue);
        }
     }
  };
Now, using the same name we have different functionality. These functions are called overloaded, as one name is loaded with more than one functionality, i.e. overloaded.

You can find this example in the OOP_CLibArray_1.mqh file in attachment to this article.

 

Another Way of Class Notation

In the examples above all the functions were written inside the class. If you have lots of functions and each one of them is pretty massive, such notation may not be very comfortable. In such cases you can place functions outside the class. Inside the class you write only names of functions with parameters, and the functions are fully described outside the class. Besides, you have to indicate that function belongs to a specific class: first write the class name, then put two colons and the function name:

class CLibArray 
  {
private:
   int               Find(int  &aArray[],int  aValue);
   int               Find(long &aArray[],long aValue);
public:
   void              AddToEnd(int  &aArray[],int  aValue);
   void              AddToEnd(long &aArray[],long aValue);
   void              AddToEndIfNotExistss(int  &aArray[],int  aValue);
   void              AddToEndIfNotExistss(long &aArray[],long aValue);
  };
//---
int CLibArray::Find(int &aArray[],int aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
int CLibArray::Find(long &aArray[],long aValue) 
  {
   for(int i=0; i<ArraySize(aArray); i++) 
     {
      if(aArray[i]==aValue) 
        {
         return(i);
        }
     }
   return(-1);
  }
//---
void CLibArray::AddToEnd(int &aArray[],int aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEnd(long &aArray[],long aValue) 
  {
   int m_size=ArraySize(aArray);
   ArrayResize(aArray,m_size+1);
   aArray[m_size]=aValue;
  }
//---
void CLibArray::AddToEndIfNotExistss(int &aArray[],int aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }
//---
void CLibArray::AddToEndIfNotExistss(long &aArray[],long aValue) 
  {
   if(Find(aArray,aValue)==-1) 
     {
      AddToEnd(aArray,aValue);
     }
  }

With such a notation you can get a full picture of the class composition and take a closer look at individual functions if necessary.

You can find this example in the OOP_CLibArray_2.mqh file in attachment to this article.

 

Declaring Variables in Class

Let's continue to consider the example mentioned earlier. There is one difference between coding directly in the file and inside the class. Directly in the file you can assign variables with values ​​as you declare them:

int Var = 123;

If you declare a variable in a class you can't do this - values ​​should be assigned when running some function of a class. So, first of all you need to pass parameters to class (i.e. prepare class to work). Lets name this function as Init().

Consider it on a practical example.

 

Example of Converting Script into Class

Suppose there is a script that deletes pending orders (see the OOP_sDeleteOrders_1.mq5 file in attachment).

// Include file to use the CTrade class from standard delivery
#include <Trade/Trade.mqh>

// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Load the CTrade class
CTrade Trade;
//---
void OnStart()
  {
// Variable to check function result
   bool Ret=true;
// Loop by all orders in terminal
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong Ticket=OrderGetTicket(i); // Select order and get its ticket
                                      // Successfully selected
      if(Ticket>0)
        {
         long Type=OrderGetInteger(ORDER_TYPE);
         // Check order type
         if(Type == ORDER_TYPE_BUY_STOP && !BuyStop) continue;
         if(Type == ORDER_TYPE_SELL_STOP && !SellStop) continue;
         if(Type == ORDER_TYPE_BUY_LIMIT && !BuyLimit) continue;
         if(Type == ORDER_TYPE_SELL_LIMIT && !SellLimit) continue;
         if(Type == ORDER_TYPE_BUY_STOP_LIMIT && !BuyStopLimit) continue;
         if(Type == ORDER_TYPE_SELL_STOP_LIMIT && !SellStopLimit) continue;
         // Check symbol
         if(!AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
         // Delete
         if(!Trade.OrderDelete(Ticket))
           {
            Ret=false; // Failed to delete
           }
        }
      // Failed to select order, unknown result,
      // function ended up with error
      else
        {
         Ret=false;
         Print("Error selecting order");
        }
     }

   if(Ret)
     {
      Alert("Script ended successfully");
     }
   else    
     {
      Alert("Script ended up with error, see details. in Journal");
     }
  }

The script has external parameters that allows to enable various types of orders and select the symbol, for which orders will be deleted (all the symbols or symbol of chart on which script is running).

Convert this script to class called COrderDelete. In the private section let's declare the same variables that are declared in the script as external parameters, but prefix variable names with "m_" (from the word "member", i.e. member of class). Adding prefix is ​​not required, but it is very convenient because it allows to easily distinguish variables. Thus we can know for sure that we are dealing with variables limited by class space. In addition, you won't get compiler warnings that variable declaration hides variable declared at global scope.

Using the same variable names at global scope, in class definition, in function body is not an error, but makes the program difficult to understand, so that's why in such cases compiler makes a warning. To assign variables with values ​​write the Init() function with parameters corresponding to these variables (and to external parameters of the script). If you use this class, first you have to call the Init() function and pass external parameters into it. The rest of the script code remains unchanged. The only exception - instead of directly using the external parameters you should use variables declared within the class.

So we get the following class:

#include <Trade/Trade.mqh> 

class COrderDelete 
  {

private:
   // Variables for parameters
   bool              m_AllSymbol;
   bool              m_BuyStop;
   bool              m_SellStop;
   bool              m_BuyLimit;
   bool              m_SellLimit;
   bool              m_BuyStopLimit;
   bool              m_SellStopLimit;
   // Load the CTrade class
   CTrade            m_Trade;
public:
   // Function to set parameters
   void Init(bool aAllSymbol,bool aBuyStop,bool aSellStop,bool aBuyLimit,bool aSellLimit,bool aBuyStopLimit,bool aSellStopLimit) 
     {
      // Set parameters
      m_AllSymbol    =aAllSymbol;
      m_BuyStop      =aBuyStop;
      m_SellStop     =aSellStop;
      m_BuyLimit     =aBuyLimit;
      m_SellLimit    =aSellLimit;
      m_BuyStopLimit =aBuyStopLimit;
      m_SellStopLimit=aSellStopLimit;
     }
   Main function to delete orders
   bool Delete() 
     {
      // Variable to check function result
      bool m_Ret=true;
      // Loop by all orders in terminal
      for(int i=0; i<OrdersTotal(); i++) 
        {
         // Select order and get its ticket
         ulong m_Ticket=OrderGetTicket(i);
         // Successfully selected
         if(m_Ticket>0) 
           {
            long m_Type=OrderGetInteger(ORDER_TYPE);
            // Check order type
            if(m_Type == ORDER_TYPE_BUY_STOP && !m_BuyStop) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP && !m_SellStop) continue;
            if(m_Type == ORDER_TYPE_BUY_LIMIT && !m_BuyLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_LIMIT && !m_SellLimit) continue;
            if(m_Type == ORDER_TYPE_BUY_STOP_LIMIT && !m_BuyStopLimit) continue;
            if(m_Type == ORDER_TYPE_SELL_STOP_LIMIT && !m_SellStopLimit) continue;
            // Check symbol/s61>
            if(!m_AllSymbol && Symbol()!=OrderGetString(ORDER_SYMBOL)) continue;
            // Delete
            if(!m_Trade.OrderDelete(m_Ticket)) 
              {
               m_Ret=false; // Failed to delete
              }
           }
         // Failed to select order, unknown result,
         // function ended up with error
         else 
           {
            m_Ret=false;
            Print("Error selecting order");
           }
        }
      // Return function result
      return(m_Ret);
     }
  };
You can find example of this class in the OOP_CDeleteOrder_1.mqh file in attachment to this article. The script using this class is reduced to a minimum (external parameters, load class, call the Init() and Delete() methods):
// External parameters

// Select symbol. true  - delete orders for all symbols, 
//                false - only for symbol of chart, where the script is running
input bool AllSymbol=false;

// Select types of orders to delete
input bool BuyStop       = false;
input bool SellStop      = false;
input bool BuyLimit      = false;
input bool SellLimit     = false;
input bool BuyStopLimit  = false;
input bool SellStopLimit = false;

// Include file with class
#include <OOP_CDeleteOrder_1.mqh> 

// Load class
COrderDelete od;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pass external parameters to the class
   od.Init(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);
// Delete orders
   bool Ret=od.Delete();
// Process result of deleting
   if(Ret) 
     { 
       Alert("Script ended successfully"); 
     }
   else    
     { 
       Alert("Script ended up with error, see details in Journal"); 
     }
  }

You can find example of this script in the OOP_sDeleteOrders_2.mq5 file in attachment to this article. Most of the script is processing the results of the Delete() function, thus notifying of the script results.

Now all the basic functions of the script are designed as a class located in a separate file, so you can use this class from any other program (Expert Advisor or Script), i.e. call this script from Expert Advisor.

 

Little Bit of Automatics (Constructor and Destructor)

The program operation can be divided into three phases: launching the program, working process and the completion of its work. The importance of this separation is obvious: when program starts it prepares itself (for example, loads and sets parameters to work with), when program ends it must make a "clean up" (e.g. remove graphical objects from chart).

To separate these stages Expert Advisors and indicators have special functions: OnInit() (runs at startup) and OnDeinit() (running at shutdown). Classes have the similar features: you can add functions that will be automatically executed when class is loaded and when it is unloaded. These functions are called Constructor and Destructor. Add a constructor to class means add a function with exactly the name as the class name. To add a destructor - do everything the same as for the constructor, but the function name begins with a tilde "~".

A script that demonstrates the constructor and destructor:

// Class
class CName 
  {
public:
   // Constructor
                     CName() { Alert("Constructor"); }
   // Destructor
                    ~CName() { Alert("Destructor"); }

   void Sleep() { Sleep(3000); }
  };

// Load class
CName cname;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart() 
  {
// Pause
   cname.Sleep();
  }

This class actually has only one function Sleep() that pauses for 3 seconds. When you run the script, an alert window with the message "Constructor" appears, then after a three-second pause, an alert window with the message "Destructor" is displayed. This is despite the fact that the CName() and ~CName() functions were never called explicitly.

You can find this example in the OOP_sConstDestr_1.mq5 file in attachment to this article.

 

Passing Parameters to Constructor

In the example where we converted script into class, you can still reduce the amount of code by one line - get rid of calling the Init() function. Parameters can be passed to constructor when loading the class. Add constructor to the class:

COrderDelete(bool aAllSymbol     = false,
             bool aBuyStop       = false,
             bool aSellStop      = false,
             bool aBuyLimit      = false,
             bool aSellLimit     = false,
             bool aBuyStopLimit  = false,
             bool aSellStopLimit=false) 
  {
   Init(aAllSymbol,aBuyStop,aSellStop,aBuyLimit,aSellLimit,aBuyStopLimit,aSellStopLimit);
  }

The Init() function remains as it was, but it is called from constructor. All parameters in constructor are optional, so that class can be used as before: load class with no parameters and call the Init() function.

After you create a constructor there is another way to use this class. When the class is loaded you can pass parameters into it with no need to call the Init() function:

COrderDelete od(AllSymbol,BuyStop,SellStop,BuyLimit,SellLimit,BuyStopLimit,SellStopLimit);

The Init() function was left in the public section to allow class reinitialization. When you use the program (Expert Advisor), in one case you may need to remove only the Stop orders, in other - only the Limit orders. To do this, you can call the Init() function with different parameters so that the Delete() function will delete a different set of orders.

You can find this example in the OOP_CDeleteOrder_2.mqh and OOP_sDeleteOrders_3.mq5 files in attachment to this article.

 

Using Multiple Instances of a Class

As it was mentioned in the previous section, the same class can perform different actions depending on parameters being set during initialization. If you know what purpose your class will be used for, you can omit class reinitialization. To do this, you should load a few instances of class with different parameters.

For example, it is known that when our EA is running, in some cases we need to delete the BuyStop and BuyLimit orders, while in other cases - SellStop and SellLimit orders. In this case, you can load two instances of the class.

To delete the BuyStop and BuyLimit orders:

COrderDelete DeleteBuy(false,true,false,true,false,false,false);

To delete the SellStop and SellLimit orders:

COrderDelete DeleteSell(false,false,true,false,true,false,false);

Now, when you want to delete the Buy pending orders use one instance of a class:

DeleteBuy.Delete();

When you want to delete the Sell pending orders - another instance:

DeleteSell.Delete();

 

Array of Objects

You may not always know for sure how many class instances you will need when your program is running. In this case you can create an array of class instances (objects). Let's take a look at this on example of class with constructor and destructor. Slightly altering the class, let's pass parameter to the constructor, so we can monitor each instance of the class:

// Class
class CName 
  {
private:
   int               m_arg; // Variable for the instance

public:
   // Constructor
   CName(int aArg) 
     {
      m_arg=aArg;
      Alert("Constructor "+IntegerToString(m_arg));
     }
   // Destructor
  ~CName() 
     { 
      Alert("Destructor "+IntegerToString(m_arg)); 
     }
   //---
   void Sleep() 
     { 
      Sleep(3000); 
     }
  };
Let's use this class. You can declare an array of a certain size, for example ten elements:
CName* cname[10];

See one difference from the usual array of variables declaration - an asterisk "*". An asterisk tells that the dynamic pointer is used in contrast to previously used automatic pointer.

You can use a dynamic array (without preallocated size, don't confuse dynamic array with dynamic pointer):

CName* cname[];

In this case it will require scaling (performed inside any function, in scripts - inside the OnStart() function):

ArrayResize(cname,10);

Now let's loop through all elements of the array and load class instance into each of them. To do this use the new keyword :

ArrayResize(cname,10);
for(int i=0; i<10; i++) 
  {
   cname[i]=new CName(i);
  }
Pause:
cname[0].Sleep();

Check the script. Run it and see that there are ten constructors, but no destructors. If you use dynamic pointers, classes are not unloaded automatically when program is terminated. In addition to that, on the "Experts" tab you can see messages about memory leaks. You should delete objects manually:

for(int i=0; i<10; i++) 
  {
   delete(cname[i]);
  }

Now, at the end of the script there are ten destructors running and no error messages.

You can find this example in the OOP_sConstDestr_2.mq5 file in attachment to this article.

 

Using OOP to Change Program Logic (Virtual Functions, Polymorphism)

Polymorphism - perhaps this is the most interesting and significant OOP feature, that allows you to control the logic of your program. It makes use of a base class with virtual functions and multiple child classes. One class can take several forms defined by child classes.

Take a simple example - comparison of two values. There can be five versions of comparison: greater than (>), less than (<), greater than or equal to (>=), less than or equal to (<=), equal to (==).

Create a base class with virtual function. Virtual function - is exactly the same normal function, but its declaration starts with the word virtual:

class CCheckVariant 
  {
public:
   virtual bool CheckVariant(int Var1,int Var2) 
     {
      return(false);
     }
  };

Virtual function has no code. It is sort of a connector that will accept various devices. Depending on the type of device it will perform different actions.

Create five child classes:

//+------------------------------------------------------------------+
//|   >                                                              |
//+------------------------------------------------------------------+

class CVariant1: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <                                                              |
//+------------------------------------------------------------------+
class CVariant2: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<Var2);
     }
  };
//+------------------------------------------------------------------+
//|   >=                                                             |
//+------------------------------------------------------------------+
class CVariant3: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1>=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   <=                                                             |
//+------------------------------------------------------------------+
class CVariant4: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1<=Var2);
     }
  };
//+------------------------------------------------------------------+
//|   ==                                                             |
//+------------------------------------------------------------------+
class CVariant5: public CCheckVariant
  {
   bool CheckVariant(int Var1,int Var2)
     {
      return(Var1==Var2);
     }
  };

Before using this class it must be loaded. If you know what child class should be used, you can declare a pointer with the type of this child. For example, if you want to check the ">" condition:

CVariant1 var; // Load class to check the ">" condition

If, as in our case, we don't known in advance the type of child, a pointer to a class is declared with the type of base class. But in this case the dynamic pointer is used.

CCheckVariant* var;

In this child must be loaded using the new keyword. Load child depending on selected variant:

// Number of variant
int Variant=5; 
// Depending on variant number one of five children classes will be used
switch(Variant) 
  {
    case 1: 
       var = new CVariant1;
       break;
    case 2: 
       var = new CVariant2;
       break;
    case 3: 
       var = new CVariant3;
       break;
    case 4: 
       var = new CVariant4;
       break; 
    case 5: 
       var = new CVariant5;
       break; 
 }

Check conditions:

bool rv = var.CheckVariant(1,2);

The result of comparing two values ​​will depend upon the child class, even though the code that checks the conditions is identical for all cases.

You can find this example in the OOP_sVariant_1.mq5 file in attachment to this article.

 

More About Encapsulation (private, protected, public)

For this moment it is pretty clear with the public section - in contains functions and variables that must be visible to the class user (by user we imply a programmer, writing programs using a ready made class.) From a class user perspective there is no difference between the protected and private sections - functions and variables in these sections are not available to the user:

//+------------------------------------------------------------------+
//|   Class with the protected keyword                               |
//+------------------------------------------------------------------+
class CName1
  {
protected:
   int ProtectedFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(ProtectedFunc(aArg));
     }
  };
//+------------------------------------------------------------------+
//|   Class with the private keyword                                 |
//+------------------------------------------------------------------+
class CName2
  {
private:
   int PrivateFunc(int aArg)
     {
      return(aArg);
     }
public:
   int PublicFunction(int aArg)
     {
      return(PrivateFunc(aArg));
     }
  };

CName1 c1; // Load class with the protected keyword
CName2 c2; // Load class with the private keyword
In this example there are two classes: CName1 and CName2. Each class has two functions: one is located in the public section, other - in the protected section (for class CName1) or in the private section (for class CName2). Both classes have only one function from the public section in the drop down list of functions (Figures 2 and 3).

Figure 2. Functions of the CName1 class
Figure 2. Functions of the CName1 class

Figure 3. Functions of the CName2 class
Figure 3. Functions of the CName2 class

You can find this example in the OOP_sProtPriv_1.mq5 file in attachment to this article.

The private and protected sections determine the visibility of base class function to its child classes:

//+------------------------------------------------------------------+
//|   Base class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
protected:
   string ProtectedFunc()
     {
      return("CBase ProtectedFunc");
     }
private:
   string PrivateFunc()
     {
      return("CBase PrivateFunc");
     }
public:
   virtual string PublicFunction()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class                                                    |
//+------------------------------------------------------------------+

class Class: public CBase
  {
public:
   string PublicFunction()
     {
      // With this line everything compiles correctly
      return(ProtectedFunc());
      // If you will uncomment this line and comment the previous one, there will be a compiler error
      // return(PrivateFunc()); 
     }
  };

In this example, we have base class called CBase and child class called Class. Try to call the base class function located in the protected and private sections from child class. If you call function from the protected section, everything compiles and runs. If you call function from the private section, a compiler error is displayed (cannot call private member function). That is, function from the private section is not visible to child classes.

The protected section protects functions only from class users, and the private section also protects functions from child classes. Visibility of base class functions (located in different sections) from child classes is shown in Figure 4.

Figure 4. Visibility of base class functions from child class
Figure 4. Visibility of base class functions from child class
Blue arrows - functions are available, gray - not available.

You can find this example in the OOP_sProtPriv_2.mq5 file in attachment to this article.

 

Default Virtual Function and Inheritance

Not all virtual functions in base class must have the corresponding functions in child classes. If a child class has the same name function - it will use this very function, if not - it will run code from base class virtual function. Consider it on example.

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      string str="";
      str="Function ";
      str=str+"of base ";
      str=str+"class";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      string str="";
      str="Function ";
      str=str+"of child ";
      return(str);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {

  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   Alert("1: "+c1.Function()); // Running function from Class1
   Alert("2: "+c2.Function()); // Running function from CBase
  }

Despite the fact that the Class2 class has no functions, it is still possible to call the Function() function from it. This will run function from the CBase class. The Class1 class will run its own function:

void OnStart() 
   {
    Alert("1: " + c1.Function()); // Running function from Class1
    Alert("2: " + c2.Function()); // Running function from CBase
   }

From the class user point of view, when using a child class all the functions of base class from the public section will be available. This is called inheritance. If function of base class is declared as virtual, it will be replaced by function of child class if a child class has a function with this name (Figure 5).

Figure 5. Accessing Functions by Class Users
Figure 5. Accessing Functions by Class Users

Except the case when child class has no functions corresponding to virtual functions of base class, child class may have "extra" functions (functions with no virtual functions of the same name inside base class). If you load class using pointer to the child class type, these functions will be available. If you load class using pointer to the base class type, these functions will not be available (Figure 6).

Figure 6. Visibility of "Extra" Functions
Figure 6. Visibility of "extra" function (red arrow) is determined
by type of pointer used to load the class.

You can find this example in the OOP_sDefaultVirtual_1.mq5 file in attachment to this article.

 

Little More About Class Loading

When you use virtual functions and, accordingly, base class and child classes, if you know what child class should be used, you can use a pointer that corresponds to the child class:

Class1 c1; // Load class 1
Class2 c2; // Load class 2

If it is not known what child class should be used, then use a dynamic pointer to the base class type and load class using the new keyword:

CBase *c; // Dynamic pointer 
void OnStart() 
   {
      c=new Class1; // Load class
      ...

If you use automatic pointer to base class

CBase c; // Automatic pointer

the base class will be used as is. When you call its virtual functions, it will run code located within these functions. Virtual functions are converted into ordinary functions.  

 

Processing Objects in Function

The title of this section is self sufficient. Pointers to objects can be passed to functions, and then inside function you can call object functions. Function parameter can be declared with the type of base class. This makes function universal. A pointer to a class can be passed to function only by reference (indicated by the & mark):

//+------------------------------------------------------------------+
//|   Base Class                                                     |
//+------------------------------------------------------------------+
class CBase
  {
public:
   virtual string Function()
     {
      return("");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class Class1: public CBase
  {
public:
   string Function()
     {
      return("Class 1");
     }
  };
//+------------------------------------------------------------------+
//|   Child class 2                                                  |
//+------------------------------------------------------------------+
class Class2: public CBase
  {
public:
   string Function()
     {
      return("Class 2");
     }
  };

Class1 c1; // Load class 1
Class2 c2; // Load class 2
//+------------------------------------------------------------------+
//|   Function to process objects                                    |
//+------------------------------------------------------------------+
void Function(CBase  &c)
  {
   Alert(c.Function());
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
// Process objects using one function.
   Function(c1);
   Function(c2);
  }
You can find this example in the OOP_sFunc_1.mq5 file in attachment to this article.

 

Functions and Methods, Variables and Properties

So far in this article we used the word "function". But in OOP instead of the word "function" programmers often use the word "method". If you look at the class from within from the perspective of a programmer writing a class, all the functions remain functions. If you look at the class from the perspective of a programmer using a ready made class, then functions of class interface located in the public section (available in the drop-down list after typing a dot) are called methods.

In addition to methods class interface may include properties of class. The public section can include not only functions, but also variables (including arrays).

class CMethodsAndProperties 
   {
    public:
        int               Property1; // Property 1
        int               Property2; // Property 2
        void Function1() 
           {
            //...
            return;
           }
        void Function2() 
           {
            //...
            return;
           }
   };

These variables will be called class properties and will also be available in the drop-down list (Figure 7).

Figure 7. Methods and properties of class in one list
Figure 7. Methods and properties of class in one list

You can use these properties in the same way as variables:

void OnStart() 
   {
    c.Property1 = 1; // Set property 1
    c.Property2 = 2; // Set property 2

    // Read properties
    Alert("Property1 = " + IntegerToString(c.Property1) + ", Property2 = " + IntegerToString(c.Property2));
   }

You can find this example in the OOP_sMethodsAndProperties.mq5 file in attachment to this article.

 

Data Structures

Data structures are similar to classes, but just a little bit easier. Although you can put it that way: classes are like data structures, but rather more complicated. The difference is that data structures may include only variables. In this regard, there is no need to divide them into the public, private and protected sections. All the contents of structure are already located in the public section. Data structure starts with the struct word, followed by the name of structure, inside the braces you declare variables.

struct Str1 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
   };

In order to use a structure it must be declared as a variable, but instead of the variable type use the name of structure.

Str1 s1;

You can also declare an array of structures:

Str1 sar1[];

Structures can include not only variables and arrays, but also other structures:

struct Str2 
   {
    int    IntVar;
    int    IntArr[];
    double DblVar[];
    double DblArr[];
    Str1   Str;
   };

In this case, to call variable from structure 1 that is part of structure 2 you must use two points:

s2.Str.IntVar=1;

You can find this example in the OOP_Struct.mq5 file in attachment to this article.

Classes can include not only variables, but also structures.

 

Conclusion

Let's review the main points of object-oriented programming and important moments to keep in mind:

1. Class is created using the class keyword, followed by class name, and then inside the braces comes the code of class written in three sections.

class CName 
  {
private:

protected:

public:
  };

2. Functions and variables of class can be located in one of three sections: private, protected and public. Functions and variables from the private section are available only within the class. Functions and variables from the protected section available within the class and from child classes. Functions and variables from the public section are available for all.

3. Class functions may be located inside or outside the class. If you place functions outside the class you must identify what class they belong to by placing the class name and two colons before each function name:

void ClassName::FunctionName() { ... }

4. Class can be loaded using both automatic and dynamic pointer. When using dynamic pointer class should be loaded using the new keyword. In this case, you have to delete an object using the delete keyword when terminating your program.

5. To tell that child class belongs to base class, you have to add the base class name after to the name of a child class.

class Class : public CBase { ... }

6. You can't assign variables with values during class initialization. You can assign values ​​while running some function, more often - the constructor.

7. Virtual functions are declared using the virtual keyword. If child class has a function with the same name it runs this very function, otherwise - runs virtual function of the base class.

8. Pointers to classes can be passed to functions. You can declare function parameters with the type of base class, so that you can pass a pointer to any child class into function.

9. The public section have not only functions (methods), but also variables (properties).

10. Structures can include arrays and other structures.

 

List of Attached Files

  • OOP_CLibArray_1.mqh - included file, should be placed in the MQL5/Include folder. Example of using class to create a library. The protected and private keywords. Overload.
  • OOP_CLibArray_2.mqh - included file, should be placed in the MQL5/Include folder. Example of placing class functions beyond the class.
  • OOP_sDeleteOrders_1.mq5 - script, should be placed in the MQL5/Scripts folder. Simple script to delete pending orders.
  • OOP_CDeleteOrder_1.mqh - included file, should be placed in the MQL5/Include folder. Example of converting the OOP_sDeleteOrders_1 script into class.
  • OOP_sDeleteOrders_2.mq5 - included file, should be placed in the MQL5/Scripts folder. Example of using class to delete orders. Taken from the OOP_CDeleteOrder_1.mqh file (setting parameters via the Init() function).
  • OOP_sConstDestr_1.mq5 - script, should be placed in the MQL5/Scripts folder. Constructor and destructor demo.
  • OOP_CDeleteOrder_2.mqh - included file, should be placed in the MQL5/Include folder. Class that deletes orders with constructor and passing parameters via constructor.
  • OOP_sDeleteOrders_3.mq5 - script, should be placed in the MQL5/Scripts folder. Example of using class to delete orders. Taken from the OOP_CDeleteOrder_2.mqh file (setting parameters via constructor).
  • OOP_sConstDestr_2.mq5 - script, should be placed in the MQL5/Scripts folder. Example of loading classes into array.
  • OOP_sVariant_1.mq5 - script, should be placed in the MQL5/Scripts folder. Example of base class with children. Virtual function, polymorphism.
  • OOP_sProtPriv_1.mq5 - script, should be placed in the MQL5/Scripts folder. An example of identity of the protected and private keywords when using a class.
  • OOP_sProtPriv_2.mq5 - script, should be placed in the MQL5/Scripts folder. Example of affecting the protected and private keywords on child class.
  • OOP_sDefaultVirtual_1.mq5 - script, should be placed in the MQL5/Scripts folder. Example of child class that has no function that corresponds to virtual function of base class.
  • OOP_sFunc_1.mq5 - script, should be placed in the MQL5/Scripts folder. Example of using objects in a function.
  • OOP_sMethodsAndProperties.mq5 - script, should be placed in the MQL5/Scripts folder. Example of properties.
  • OOP_Struct.mq5 - script, should be placed in the MQL5/Scripts folder. Example of structures.

After experimenting with these files you can delete all of them except OOP_CDeleteOrder_2.mqh and OOP_sDeleteOrders_3.mq5. The OOP_CDeleteOrder_2.mqh and OOP_sDeleteOrders_3.mq5 files may be useful in practical programming.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/351

Attached files |
files_en.zip (11.04 KB)
Last comments | Go to discussion (13)
wulidancing
wulidancing | 7 Feb 2013 at 10:18
Integer:
Now I also have a memory leak (example 1 - no, example 2 - yes). But it should not be. Nothing helps. Write about it to Service Desk (link in you profile).
I have sent a request to the Service Desk.  Thanks again, I am curious as to what the problem is.
wulidancing
wulidancing | 11 Feb 2013 at 19:47
wulidancing:
I have sent a request to the Service Desk.  Thanks again, I am curious as to what the problem is.

Here is the service desk message:

 

 

Support Team 2013.02.07 11:18
Status: Open Closed
Thank you for your message. Bug is fixed, please wait for updates.

In the current build of the terminal (756) You can fix this by adding a string type member to the class CName. 

aphong
aphong | 11 Jul 2019 at 06:52
Thanks! great article <3 ... help me alot to better understand how MQL5 standard library really works!
David_NZ
David_NZ | 4 Feb 2021 at 08:20

Hi, Where to NOW.  2021

There is a great resource in codebase.

I am not interested in writing code from scratch. Interesting reading and learning this 2012 article.

My interest is using MT5 wizard to combine several EA's then edit them for my strategy.

Are there articles following on from this one that will grow my knowledge.

Thanks

Facundo Laje
Facundo Laje | 5 Dec 2021 at 16:53
Thanks, very good article that help me to understand de basics of OOP!
Interview with Valery Mazurenko (ATC 2011) Interview with Valery Mazurenko (ATC 2011)
The task of writing an Expert Advisor trading on multiple currency pairs is complex both in terms of finding suitable strategies and from the technological side. But if the goal is set clear, nothing is impossible then. It was four times already that Vitaly Mazurenko (notused) submitted his multi-currency Expert Advisor. It seems, he has managed to find the right way this time.
Interview with Andrey Bobryashov (ATC 2011) Interview with Andrey Bobryashov (ATC 2011)
Since the first Automated Trading Championship we have seen plenty of trading robots in our TOP-10 created with the use of various methods. Excellent results were shown both by the Exper Advisors based on standard indicators, and complicated analytical complexes with weekly automatic optimization of their own parameters.
MQL5-RPC. Remote Procedure Calls from MQL5: Web Service Access and XML-RPC ATC Analyzer for Fun and Profit MQL5-RPC. Remote Procedure Calls from MQL5: Web Service Access and XML-RPC ATC Analyzer for Fun and Profit
This article describes MQL5-RPC framework that enables Remote Procedure Calls from MQL5. It starts with XML-RPC basics, MQL5 implementation and follows with two real usage examples. First example is using external web service and the second one is a client to simple XML-RPC ATC 2011 Analyzer service. If you are interested on how to implement and analyze different statistics from ATC 2011 in real time, this article is just for you.
Interview with Vitaly Antonov (ATC 2011) Interview with Vitaly Antonov (ATC 2011)
It was only this summer that Vitaly Antonov (beast) has learned about the upcoming Automated Trading Championship and got to know MetaTrader 5 terminal. Time was running out, besides, Vitaly was a newcomer. So, he randomly chose GBPUSD currency pair to develop his trading system. And the choice turned out to be successful. It would have been impossible to use other symbols with the strategy.