Overview
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.
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.
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
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:
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:
$array1 = [];
$array1[] = 1;
$array1[] = 2;
$array1[] = 3;
?>
Here we added 3 elements into the array. Let's mimic this in RubbArray class.
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.
{
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:
{
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:
a << 1 << 2 << 3;
Now, when the array is filled with some data, code a method to access elements, specifically let's overload operator[].
{
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).
{
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.
{
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:
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.
Editor<T> *editor;
and constructor/destructor:
RubbArray()
{
editor = new Editor<T>(this);
}
~RubbArray()
{
delete editor;
}
Step 3 - adding a method and new overload of operator[] to access the editor.
{
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:
Now we can code:
a[edit(0)] = 10; // write 10 to 0-th element
The complete class is attached in SafeArray.mqh at the bottom of the page.
Safe numbers
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:
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.
{
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.
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.