MQL's OOP notes: rubber arrays, safe numbers, exceptions handling and other tricks: part 1

8 November 2016, 14:54
Stanislav Korotky
1
30
Today we're starting a patchy subject, which will combine many different things. This is why I decided to split it into parts. Out final goal is exception handling, but before we can address it, we need to prepare a ground.

 

Overview

Exceptions are very powerfull and useful mechanism of modern object oriented programming languages. They exist in C++, Java, PHP, and many others. This makes me believe that exceptions are "must have" feature for a mature OOP language. The fact that they are (currently) missing in MQL diminishes its capabilities and value. This is definitely a design error from MetaQuotes.  

The reason why exceptions are so important can be easily seen in the following 2 axioms.

Every program - no matter how thoroughly it's been developed and tested - contains bugs.

Well-designed program should work stable despite the bugs.  

In MQL, if an error occures in a program, it is stopped and unloaded. This is a very frustrating feature for a program that controls your money. Far better approach would be to give the program a chance to analyse the error and recover, hence continue working if possible.

The new strict policy of MQL execution was not there from very beginning. Some time ago (before OOP epoch in MQL4) MQL4 could silently suppress part of errors. For example, when accessing an array element by incorrect index in a legacy MT4 indicator, it ignores the error and continues running. Then MQ introduced new property strict directive in MQL4, when it was upgraded to MQL5, and made the directive mandatory for the Market products. Since that moment both MQL4 and MQL5 programs (where the strict mode is always on) can not survive on errors.

The problem must thus be solved by application developers. We need to invent something similar to exceptions handling.

First let us decide which types of critical errors to handle. Most frequent errors are "index out of bounds" and "divide by zero". There is a number of others of course, but part of them can not be handled internally by MQL (for example, if a requested library or indicator is unavailable) and the rest can be addressed later in the similar way to the two most frequent ones, which are covered below. All beyond this is left for homework ;-). And here is a small hint: next best candidate could be "invalid pointer access" error, which can be processed by a kind of safe pointers class.

 

Rubber array

It's time now to develop a class of safe array which can deal with the "index out of bounds" error. Along with the error handling we can add into the class many other features which will be handy for operations with arrays. I'll name the class RubbArray (which stands for "rubber array") because its creation was initially inspired by the need to automatically adjust its size. I'm sure all of you typed a lot of times something like that:

int n = ArraySize(data);
ArrayResize(data, n + 1);
data[n] = d;

Placing such stuff inside an implicit helper method can simplify our code significantly. Of course, the class should be templatized because we want a universal array capable of storing values of different types. So start with:

template <typename T>
class RubbArray
{
  protected:
    T data[];

    void add(T d)
    {
      int n = ArraySize(data);
      ArrayResize(data, n + 1);
      data[n] = d;
    }

Data will be actually stored in the protected data array. We hide the method add in protected part as well, because no one would normally like to call it, provided that other shorthand notations can be easily implemeted. For example, in PHP we can declare and fill arrays like this:

<?php

$array1 = [];
$array1[] = 1;
$array1[] = 2;
$array1[] = 3;

?>

Here we added 3 elements into the array. Let's mimic this in RubbArray class.

  public:
    T operator=(T d)
    {
      add(d);
      return d;
    }

Now we can use the assignment operator to add new elements into the array. Similar operator can be overloaded for copying arrays.

    void operator=(const RubbArray &d)
    {
      int i, n = d.size();
      ArrayResize(data, n);
      for(i = 0; i < n; i++)
      {
        data[i] = d[i];
      }
    }

In addition to operator= we can provide another way of adding elements. For example: 

    RubbArray *operator<<(T d)
    {
      add(d);
      return &this;
    }

This is almost the same as operator=, but we return the array object itself, making it possible to chain calls in a single line:

    RubbArray<double> a;
    a << 1 << 2 << 3;

Now, when the array is filled with some data, code a method to access elements, specifically let's overload operator[].

    T operator[](int i) const
    {
      if(i < 0 || i >= ArraySize(data))
      {
        Print("Index out of bounds"); // ?
        return 0;                     // ?
      }
      return data[i];
    }

The lines marked by comments are just placeholders for something we'll need to elaborate on later. But it's already a safe code: for incorrect indices it returns 0 silently, as former MQL did for time series (it actually returned EMPTY_VALUE, but we use 0 here as more universal for different types, and the value can be customized in future, as you'll see). 

Also one may want to extract an element (specified by its index) from the array.

    T operator>>(int i)
    {
      T d = this[i];
      if(i >= 0 || i < ArraySize(data))
      {
        ArrayCopy(data, data, i, i + 1);
        ArrayResize(data, ArraySize(data) - 1);
      }
      return d;
    }

Notice how we used operator[] internally.

And what if we want to change an element? Well, since MQL's overloads are still far from ideal (in comparison to C++ or Pascal for example), it'll require a touch of voodoo. First, add a plain method for changing.

    void change(int i, T value)
    {
      data[i] = value;
    }

Now we need somehow call it in a neat way. This can be done in 3 steps. First create a helper class:

template <typename T>
class Editor
{
  RubbArray<T> *parent;
  int index;
  
  public:
    Editor(RubbArray<T> &array)
    {
      parent = &array;
    }
    Editor<T> *operator[](int i)
    {
      index = i;
      return &this;
    }
    void operator=(T value)
    {
      if(index != -1) // special case of error (see below)
      {
        parent.change(index, value);
      }
    }
};

As you may see, its sole purpose is to override operator[] and operator= to pass required index and value to the change method of RubbArray. Editor is created for specific RubbArray and stores a reference for the parent.

In turn RubbArray class should manage Editor object for itself, so add the following member variable into RubbArray (step 2):

  private:
    Editor<T> *editor;

and constructor/destructor: 

  public:
    RubbArray()
    {
      editor = new Editor<T>(this);
    }
    
    ~RubbArray()
    {
      delete editor;
    }

Step 3 - adding a method and new overload of operator[] to access the editor.

    Editor<T> *operator[](uint i) // notice different type 'uint', this is a hack
    {
      return edit(i);
    }
    
    Editor<T> *edit(int i)
    {
      if(i < 0 || i >= ArraySize(data))
      {
        return editor[-1]; // do nothing in case of error
      }
      return editor[i];
    }

For convenient usage of the new overload one may define a macro:

#define edit(A) ((uint)A)

Now we can code:

 Print(a[0]); // read 0-th element
 a[edit(0)] = 10; // write 10 to 0-th element

The complete class is attached in SafeArray.mqh at the bottom of the page.

As you remember we marked a couple of lines in RubbArray for potential "index out of bounds" error handling. Let us leave the stuff as is for a while, and switch to the other typical error "divide by zero".

 

Safe numbers

The problem with division by zero should be handler by another class. Name it Number and make templatized as well.

template<typename T>
class Number
{
  private:
    T v;
    double eps;
    void init(const double e)
    {
      eps = e == 0 ? MathPow(10, -1 * _Digits) : e;
    }

The class includes several things not related to error handling just for making hook-ups, and can be extended further. For example, the eps variable is intended for comparison of 2 numbers with a given accuracy, which is usually important for doubles. We can construct or assign a value to the Number in straightforward manner:

  public:
    Number() {init(0);}
    Number(const T &d, const double e = 0): v(d) {init(e);}
    Number(T d, const double e = 0): v(d) {init(e);}
    Number(const Number<T> &c): v(c.v) {init(c.eps);}
    
    T operator=(T x)
    {
      v = x;
      return v;
    }
    Number<T> operator=(const Number<T> &x)
    {
      v = x.v;
      return this;
    }
    bool operator==(T x) const
    {
      return MathAbs(v - x) < eps;
    }
    bool operator!=(T x) const
    {
      return !(this == x);
    }

Also Number should overload all possible operations, including division, which is actually our main concern.

    T operator*(Number<T> &x) const
    {
      return v * x.v;
    }

    T operator/(Number<T> &x) const
    {
      if(x.v == 0)
      {
        Print("Divide by zero"); // ?
        return 0;                // ?
      }
      return v / x.v;
    }

Again, the commented lines of code will require error handling in future. Currently we return 0, suppressing the error.

The complete code of the example class Number is attached in SafeNumbers.mqh file at the bottom of the page.

Now all is ready to deal with exceptions. But we already typed a lot of code, so let us take a break and continue with this in the part 2.


Files:
Share it with friends: