MQL5でのオブジェクト作成と削除の順番

MetaQuotes | 25 8月, 2015

この本記事のトピックは?

MQL5プログラムは オブジェクト指向プログラミング (OOP)コンセプトで書かれており、カスタムライブラリを作成する新しい可能性が開けただけでなく、他の開発者の完全で試験済のクラスを使うことができるようになりました。MetaTrader 5 クライアントターミナルの中にある 標準 ライブラリでは数千のメソッドを含む数百のクラスがあります。

OOPをフルに活用するには、 MQL5プログラムにおけるオブジェクトの作成と削除の詳細について明らかにしなければなりません。オブジェクトの作成と削除 は簡潔にドキュメンテーションに書かれています。本記事はこのトピックを例で説明します。


グローバル変数の初期化と非初期化

グローバル変数の初期化 はMQL5プログラムの開始直後と関数呼び出し前に行われます。初期化中、初期データはシンプルタイプの変数に割り当てられ、そこで宣言されていればオブジェクトのコンストラクターが呼ばれます。

例として、 CObjectAとCObjectBの2つのクラスを宣言しましょう。全てのクラスはシンプルなPrint() 関数を持つコンストラクタとデストラクタがあります。クラスタイプの変数をグローバルに宣言し、スクリプトを実行しましょう。

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

このスクリプトの結果が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

初期化の順番が GlobalVar_TestScript.mq5 スクリプトの変数の順番の宣言にマッチし, 非初期化がMQL5プログラムのロールアウト前に逆の順番で実行されていることがジャーナルからはっきりわかります。


ローカル変数の初期化と非初期化

ローカル変数は宣言されているプログラムブロックの最後で宣言と逆の順で非初期化されます。プログラムブロックは スイッチ 演算子、ループ演算子 (for, whiledo-while)、 関数の本体 または if-else 演算子の一部の一部かもしれない 複合演算子 です

ローカル変数はプログラムで使われている時だけ初期化されます。もし変数が宣言されているが、宣言されているコードブロックが実行されない場合、この変数は作成されず、それゆえ初期化されません。

これを説明するために我々のCObjectA と CObjectB クラスに戻り、新しいクラス CObjectСを作りましょう。クラスはまだグローバルに宣言されていますが、これらのクラスの変数はOnStart() 関数でローカルに宣言されています。

CObjectA クラス の変数を関数の最初のラインで明示的に宣言しましょう。しかしCObjectB と CObjectС クラスのオブジェクト は入力変数の値に応じて実行され別のブロックで宣言されます。 MetaEditorでは、MQL5プログラムの入力変数は茶色で強調されています。

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

結果は:

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

execute入力パラメーターの値が何であれCObjectA クラス のオブジェクトは常に最初に自動で初期化されます。 objBオブジェクトまたはobjCオブジェクトのいずれかは自動で 初期化されます。 それは execute 入力パラメータの値に応じてどのブロックが実行されるかによります。デフォルトでは、このパラメータはfalse 値を持ちます。そしてこの場合 objA 変数の後に objC 変数の初期化が来ます。これはコンストラクタとデストラクタ実行で明白です。

初期化 の順番がどうであれ (execute パラメータにかかわらず)、複雑タイプ 変数の非初期化は初期化と逆の順番です。 これは 自動で作成されたローカルとグローバルクラス オブジェクトの両方に当てはまります。この場合、それらの間には違いがありません。


動的に作成されたオブジェクトの初期化と非初期化

MQL5では、複合オブジェクトは自動で初期化されますが、オブジェクト作成プロセスを手動でコントロールしたい場合、 オブジェクトポインターを使わなければなりません。クラスのオブジェクトポインターとして宣言されている変数はそれ自身オブジェクトを含まず、そのオブジェクトの自動初期化はありません。

ポインターはローカルにそして/またはグローバルに宣言され、同時にinherited タイプ NULL の空の値で初期化されるかもしれません。新しい 演算子がオブジェクトポインターに適用された時にオブジェクト作成は一回だけ行われ、オブジェクトポインター宣言に左右されません。

動的に作成された オブジェクトはdelete 演算子を使って削除されるので、私たちが対応しなければなりません。例としてCObjectA タイプとCObjectB タイプ、そして別の変数CObjectC タイプの2つの変数をオブジェクトポインターと一緒にグローバルに宣言しましょう。

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

動的に作成されたオブジェクトポインターpObjectCは静的変数firstsecondの前に宣言される事実にもかかわらず、まさにこのオブジェクトは 新しい 演算子によって作成された時だけ初期化されます。この例では、 新しい 演算子は OnStart() 関数です。

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

OnStart()関数のプログラム実行が演算子に達すると、

pObjectC=new CObjectC;

オブジェクトが初期化され、このオブジェクトの構築が呼ばれます。そしてプログラムが以下の文字列を実行します。

Print(__FUNCTION__);

それはJournalで以下のテキストを出力します。

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

そして、delete 演算子を呼び出すことで動的に作成された オブジェクトは削除されます。

delete(pObjectC);

そのため、 オブジェクトはその作成中新しい 演算子によって、動的に初期化され、delete 演算子 によって削除されます。

必須要件:object_pointer=new Class_Nameの式によって作成された全てのオブジェクトは必ず delete(object_pointer) 演算子を使って削除されます。もしもある理由によって、動的に作成されたオブジェクト (初期化された最後のブロックの後) が delete 演算子を使って削除されなかった場合、Experts journalにメッセージが現れます。


動的に作成されたオブジェクトの削除

前述のとおり、全ての動的に作成された オブジェクトは、新しい 演算子を使って初期化され、必ずdelete 演算子を使って削除されます。しかし、新しい 演算子がオブジェクトを作成し、ポインターをそのオブジェクトに戻すことを忘れていはいけません。 作成されたオブジェクト自身は、オブジェクトポインターを含む変数にありません。いくつかのポインターを宣言しそれらに同じオブジェクトポインターを振当てることができます。

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

アウトプットは未削除のオブジェクトが残っていると示します。しかし、未削除のオブジェクトは10個ではなく5個しかないでしょう。 新しい 演算子は5つのオブジェクトしか作成しなかったためと考えるかもしれません。

(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

動的に作成されたオブジェクトのデストラクタが呼ばれないとしても (オブジェクトは delete 演算子を使って削除されません)、メモリはクリアされます。しかし、"Experts" journal では、そのオブジェクトは削除されなかったと示します。これは不適切なオブジェクト管理を見つけ、エラーを修正する助けとなります。

次の例では、array1array2の2つのポインター配列のそれぞれのポインターを削除してみましょう。

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

今ではExpertタブの結果が異なります。

(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

CItem 作成オブジェクトは最初の for() ループでうまく削除されましたが、さらに第二ループでの存在しないオブジェクトを削除しようとすると、無効ポインターについてのメッセージが発生しました。 動的に作成されたオブジェクトは一旦削除されなければなりません。そしていかなるオブジェクトポインターの使用前にもCheckPointer() 関数で確認しなければなりません。


CheckPointer()関数を使ったポインターチェック

CheckPointer() はポインターをチェックするために使用され、 pointer typeを識別することができます。動的に作成されたオブジェクトを使用する時、2つの可能性があります。

オブジェクト相関を説明した他の例を取り上げましょう。2つのクラスを作りましょう。最初の クラス CItemArray は別のクラス 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");
     }
  }

クラス自身はエラーを含みませんが、その使用には驚かされるかもしれません。スクリプトの最初の変数 :

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

この変数のスクリプト実行は以下のメッセージを表示します。

(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

CItemArray クラス変数のの宣言が最初に来るので、 最初に初期化され、クラス デストラクタが呼ばれます。そのため CItem クラス オブジェクト ポインターを含んだ、配列[5] が宣言されます。このため、各オブジェクトの初期化について5つのメッセージを見ます。

このシンプルスクリプトの最後のラインに、配列[5]配列 からのポインター がitems_array ( 「LocalVar_TestScript_4.mq5」参照)と名づけられた内部オブジェクトポインター配列にコピーされます。

items_array.SetArray(array);

今はスクリプトがストップし、自動で作成された オブジェクトは 自動で 削除されます。削除されるべき最初のオブジェクトは最後に初期化されたものです。 それは配列[5] ポインター 配列です。CItem クラス デストラクタ呼び出しについての5つのジャーナルレコード はこれを確認します。. array[5] 変数の直前で初期化されたため、そこでitems_array オブジェクトのデストラクタ呼び出しのメッセージが現れます。

しかし、 CArrayItem クラスデストラクタはm_array[] 内のポインターを通してdelete 演算子経由でCItem オブジェクトを削除しようとする保護されたDestroy() 関数を呼びます。 ポインターが最初に確認されもしも無効の場合、そのオブジェクトは削除されず、「Invalid pointer to delete」メッセージが表示されます。

Journalには5つのこのようなレコードがあります。つまり、 m_array[] 配列の全てのポインターは無効です。これは、ポインターのオブジェクトがarray[] 配列の非初期化中にすでに非初期化されているために起こります。

items_arrayitems_array[] 変数の宣言をスワップしながら我々のスクリプトを調整しましょう。

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

修正されたスクリプトはエラーを発生させません。最初、items_array 変数が非初期化され、最後に宣言されました。非初期化中、~CItemArray() クラス デストラクタが呼ばれ、そのことによって Destroy() 関数
を呼びました。

宣言のこの順番では、 array[5] 配列前に items_array が削除されます。items_array デストラクタから呼ばれるDestroy() 関数では、ポインターオブジェクトがまだ存在し、そのためエラーが起こります。

動的に作成されたオブジェクトの正しい削除は GetPointer() 関数の例でも見られます。この例ではオブジェクト削除の適切な順番を確実にするためDestroy() 関数が明示的に呼ばれます。


結論

ご覧のとおり、オブジェクト作成と削除がシンプルに実行されました。本記事の全ての例を復習すると、自動または動的に作成された オブジェクト間の相関 変数を自分で作れます。

オブジェクトが正しく削除されているかクラスを必ずチェックし、 デストラクタを適切にデザインするべきです。そうすれば無効ポインターにアクセスしたときにエラーがありません。new 演算子を使って動的に作成されたオブジェクトを使用する場合、これらのオブジェクトをdelete 演算子を使って正しく削除しなければならないことを覚えておいてください。

本記事ではMQL5におけるオブジェクト作成と削除の順番だけを学びました。 オブジェクトポインターを使ったセキュア作業の組織化は本記事の範囲外です。