2010/07/20

MT4で大量のオブジェクトを消す

MetaTrader4で大量のテキストオブジェクトをループで削除する際に、必ず一定数が消えずに残る。という現象にはまったのでメモ。

オブジェクト総数が不明の場合に、総当りの処理をするには、"ObjectsTotal"を使います。ヘルプを見るとサンプルコードが載っています。これが罠ですが。

  1. int    obj_total=ObjectsTotal();  
  2. string name;  
  3. for(int i=0;i<obj_total;i++)  
  4.   {  
  5.    name=ObjectName(i);  
  6.    Print(i,"Object name for object #",i," is " + name);  
  7.   }  

オブジェクトはindex番号で探しますが、削除する関数"ObjectDelete"では名前で対象を指定します。なので、あわせるとこんな感じになります。

  1. int    obj_total=ObjectsTotal();  
  2. string name;  
  3. for(int i=0;i<obj_total;i++)  
  4.   {  
  5.    name=ObjectName(i);  
  6.    ObjectDelete(name);  
  7.   }  

これで消えるかと思ったら、消えません。index番号をログに出して見ると必ず後半半分が失敗します。600個なら300個、300個なら150個消え残ります。

どうも、ObjectDeleteは削除するたびにオブジェクト配列を一つ前にシフトするみたいです。考えて見れば当たり前ですが。

  1. object[0] = "a"  
  2. object[1] = "b"  
  3. object[2] = "c"  
  4. object[3] = "d"  
  5.   
  6. for (int i=0; i<4; i++) {  
  7.  name = ObjectName(i);  
  8.  ObjectDelete(name);  
  9. }  

このような配列をループで消す場合のイメージですが、

  1. //1周目  
  2.  i==0  
  3.  name == "a"  
  4. //削除後の配列  
  5.  object[0] = "b"  
  6.  object[1] = "c"  
  7.  object[2] = "d"  
  8.   
  9. //2周目  
  10.  i==1  
  11.  name == "c"  
  12. //削除後の配列  
  13.  object[0] = "b"  
  14.  object[1] = "d"  
  15.   
  16. //3周目  
  17.  i==2  
  18.  name == "" // GetError() == 4202:ERR_OBJECT_DOES_NOT_EXIST  
  19. //削除後の配列  
  20.  object[0] = "b"  
  21.  object[1] = "d"  
  22.   
  23. //4周目  
  24.  i==3  
  25.  name == "" // GetError() == 4202:ERR_OBJECT_DOES_NOT_EXIST  
  26. //削除後の配列  
  27.  object[0] = "b"  
  28.  object[1] = "d"  

ベタなオチですが、MTは配列の操作を暗黙的に行う場合が多いので気づかないとハマりますね。コードが"objects.shift()"なら気が付いたかもしれませんが。

結局、全部消す場合、後ろからデクリメントしていくか、だるま落としの様に常に0を消していくかですかね。

  1. int    obj_total=ObjectsTotal();  
  2. string name;  
  3. for(int i=obj_total-1;0<=i;i--) // デクリメント  
  4.   {  
  5.    name=ObjectName(i);  
  6.    ObjectDelete(name);  
  7.   }  
  1. int    obj_total=ObjectsTotal();  
  2. string name;  
  3. for(int i=0;i<obj_total;i++)  
  4.   {  
  5.    name=ObjectName(0); // 常に先頭の要素  
  6.    ObjectDelete(name);  
  7.   }  

試してませんが、後者の方が負荷大きそうな気がしますね。