MQL5のコードを読んでいると、
`CObject *obj = new CObject();`のような、
見慣れない`*`付きの書き方に出会うことがあります。
これは「オブジェクトポインタ」と呼ばれる、MQL5特有の仕組みです。
この記事では、なぜこの仕組みが必要なのか、
`new`・`delete`・`CheckPointer`・
`GetPointer`の使い方を、
直近N件のログだけを保持する実際に動くコードで解説します。
・new演算子でオブジェクトを動的に生成する方法
・delete演算子で解放しないとメモリリークする理由
・CheckPointer/GetPointerによる安全確認
・個数が変わるオブジェクトをポインタ配列で管理する実装例
オブジェクトポインタとは何か
まず、この記事が扱う仕組みの全体像を確認します。
変数とポインタの感覚の違い
CMyClass obj; //普通の変数(自動オブジェクト) CMyClass *ptr = new CMyClass();//ポインタ(動的オブジェクト)
`obj`はこの関数のスコープを抜けると自動的に消える「普通の変数」です。
`ptr`は`new`で作った実体の「識別子」であり、自分で明示的に消さない限り、
スコープを抜けても残り続けます。
なぜMQL5でポインタを使う場面があるのか
・「今いくつ必要になるか、実行中まで分からないオブジェクトの集まり」を扱いたいとき
・配列の要素数を動的に増減させながら、要素1つひとつがクラスのインスタンスであるとき
・標準ライブラリのCArrayObjも、内部でこのポインタの仕組みを使っている
MQL4にはこの概念がない
MQL4のクラスは基本的に通常の変数として扱われ、
`new`・`delete`による動的な生成・破棄という概念自体がありません。
この記事はMQL5限定の仕組みを扱います。
new演算子でオブジェクトを動的に生成する
実際にオブジェクトを動的に作る書き方を確認します。
new演算子の書式
CMyClass *ptr = new CMyClass(引数);
`new`は、指定したクラスの実体をメモリ上に作り、そのポインタ(識別子)を返します。
ポインタ自体のサイズは、クラスの内容に関わらず8バイトで固定です。
動的オブジェクトと自動オブジェクトの違い
・自動オブジェクト(普通の変数):スコープを抜けると自動で消える、個数は事前に決まっている
・動的オブジェクト(new):自分で消すまで残る、実行中に必要な個数だけ増やせる
ポインタ配列の宣言方法
CMyClass *list[]; //ポインタの配列(要素はまだ0個)
配列の各要素が「オブジェクトそのもの」ではなく「オブジェクトへのポインタ」
になる点が、通常のクラス配列との違いです。
delete演算子で解放しないとメモリリークする
動的に作ったオブジェクトを、正しく後始末する方法を確認します。
delete演算子の書式
delete ptr;//ptrが指すオブジェクトを解放する
`delete`を呼ぶと、そのオブジェクトが使っていたメモリが解放されます。
動的オブジェクトは自分で削除する責任がある
・newで作ったオブジェクトは、自動オブジェクトのように自然には消えない
・deleteを呼び忘れると、使われないメモリが終了まで残り続ける(リーク)
・EAやインジケーターを長時間動かし続けるほど、リークの影響が蓄積しやすい
OnDeinitでの一括削除パターン
void OnDeinit(const int reason)
{
for(int i = 0; i < ArraySize(list); i++)
{
if(CheckPointer(list[i]) == POINTER_DYNAMIC)
delete list[i];
}
ArrayFree(list);
}
EA・インジケーターが終了するタイミングで、残っている動的オブジェクトを必ず一括削除する、
定石の書き方です。
CheckPointerとGetPointerで安全性を確認する
ポインタを安全に扱うための、
CheckPointerとGetPointerという2つの関数を確認します。
CheckPointerの戻り値3種類
| 定数 | 意味 |
|---|---|
| POINTER_INVALID | 無効なポインタ(NULLまたは削除済み) |
| POINTER_DYNAMIC | newで動的に生成されたオブジェクト |
| POINTER_AUTOMATIC | 自動生成されたオブジェクト(通常の変数) |
ENUM_POINTER_TYPE CheckPointer(object *anyobject);
ポインタを使う前に、まずこの関数で状態を確認するのが安全な書き方です。
GetPointerで自動オブジェクトのポインタを取り出す
CMyClass autoObj; //普通の変数(自動オブジェクト) CMyClass *p = GetPointer(autoObj); //自動オブジェクトのポインタを取得 Print(CheckPointer(p)); //POINTER_AUTOMATICが表示される
`GetPointer`は、クラスオブジェクトであれば、
`new`を使っていない普通の変数からもポインタを取り出せます。
構造体や`int`・`double`等の基本データ型には使えません。
無効なポインタへアクセスすると強制終了する
・削除済み、またはNULLのポインタのメンバーにアクセスすると、プログラムが緊急終了する
・CheckPointer()で有効か確認してからアクセスするのが安全
実装: 直近N件のログだけ保持するリングバッファ
ポインタの仕組みが実際に役立つ、シンプルな例を作ります。
配列を使い回すのではなく都度オブジェクトを作る理由
ログ1件ごとに「発生時刻」と「メッセージ」という2つの情報をまとめて持たせたいとき、
クラスにしてオブジェクトとして扱うと、配列の要素を増減させる処理が直感的に書けます。
上限を超えたら最も古いオブジェクトを解放する
if(ArraySize(g_log) > InpMaxLogSize)
{
if(CheckPointer(g_log[0]) == POINTER_DYNAMIC)
delete g_log[0];//先頭(最も古い)オブジェクトを解放
}
保持件数の上限を決め、超えたら最も古いオブジェクトだけを`delete`します。
新しい確定バーを検出したときにログを追加する
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime != g_lastBarTime)
{
g_lastBarTime = currentBarTime;
return(true);
}
return(false);
}
`iTime`で0番目(形成中)の足の開始時刻を取得し、前回と違えば「新しいバーが来た」
と判定します。
実際に動くコード全体
ここまでの要素をまとめた、実際にMetaEditorでコンパイルできるコードです。
//+------------------------------------------------------------------+
//| PointerLogRingBuffer.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property indicator_chart_window
#property indicator_plots 0
input int InpMaxLogSize = 5;//保持する最新件数
class CLogEntry
{
public:
datetime time;
string message;
CLogEntry(string msg)
{
time = TimeCurrent();
message = msg;
}
};
CLogEntry *g_log[];
datetime g_lastBarTime = 0;
//+------------------------------------------------------------------+
//| 新しい確定バーを検出したか |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime != g_lastBarTime)
{
g_lastBarTime = currentBarTime;
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| ログを1件追加し、上限を超えたら最も古い1件を解放する |
//+------------------------------------------------------------------+
void AddLogEntry(string msg)
{
int size = ArraySize(g_log);
ArrayResize(g_log, size + 1);
g_log[size] = new CLogEntry(msg);
if(ArraySize(g_log) > InpMaxLogSize)
{
if(CheckPointer(g_log[0]) == POINTER_DYNAMIC)
delete g_log[0];
for(int i = 0; i < ArraySize(g_log) - 1; i++)
g_log[i] = g_log[i + 1];
ArrayResize(g_log, ArraySize(g_log) - 1);
}
}
//+------------------------------------------------------------------+
//| 保持している全ログをExpertsタブへ出力する |
//+------------------------------------------------------------------+
void PrintLog()
{
for(int i = 0; i < ArraySize(g_log); i++)
{
if(CheckPointer(g_log[i]) == POINTER_INVALID)
continue;
PrintFormat("[%s] %s", TimeToString(g_log[i].time, TIME_SECONDS), g_log[i].message);
}
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(IsNewBar())
{
AddLogEntry("新しい確定バーを検出しました");
PrintLog();
}
return(rates_total);
}
void OnDeinit(const int reason)
{
for(int i = 0; i < ArraySize(g_log); i++)
{
if(CheckPointer(g_log[i]) == POINTER_DYNAMIC)
delete g_log[i];
}
ArrayFree(g_log);
}
//+------------------------------------------------------------------+
`InpMaxLogSize`件を超えた瞬間に、
最も古いログオブジェクトだけが`delete`され、
残りのオブジェクトは配列の先頭へ詰め直されます。
MetaEditorで実際に動かす手順
- MetaTrader 5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「インジケーター」を選び、名前を「PointerLogRingBuffer」にして作成する(中身は空のテンプレートで構いません)
- テンプレートの中身を全て削除し、上のコードを丸ごと貼り付ける
- F7キー(またはコンパイルボタン)でコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- チャートへ適用し、「エキスパート」タブに新しい確定バーごとに直近5件のログが出力されることを確認する
よくあるエラーと注意点
実装・運用でつまずきやすいポイントをまとめます。
deleteし忘れてメモリリークする
・ArrayResizeで要素を減らしても、指すオブジェクト自体は解放されない
・配列から要素を取り除く前に、必ずdeleteを呼ぶ順序を守る
削除済みポインタにアクセスして強制終了する
一度`delete`したポインタを、配列から取り除かずに再度参照すると、
プログラムが強制終了します。
本記事のコードは、削除と同時に配列から詰め直すことでこれを防いでいます。
構造体や基本データ型にはGetPointerが使えない
`GetPointer`はクラスオブジェクト専用の関数です。
`struct`で定義した構造体や、
`int`・`double`等の基本データ型の変数に対して呼ぶと、
コンパイルエラーになります。
よくある質問
MQL5のオブジェクトポインタについて、よく検索される疑問に回答します。
参照渡し(&)とポインタ(*)は同じものですか?
別の仕組みです。
既刊pass-by-reference-value-mql4-mql5.mdの、
参照渡しは、
既存の変数に「別名」を付けて関数に渡す仕組みです。
一方ポインタは、newで動的に生成したオブジェクトの「識別子」そのものを指します。
標準ライブラリのCObjectとはどう関係しますか?
CObjectは、MQL5の標準ライブラリに含まれるクラスの土台(基底クラス)です。
CArrayObj等の動的配列コレクションは、
CObjectを継承したクラスのポインタを管理する仕組みで作られています。
本記事で自作したCLogEntryも、
CObjectを継承すればCArrayObjで管理できるようになります。
EAの資金管理やトレード判定にもポインタは使えますか?
使えます。
ポジションごとに「エントリー根拠」や「決済の進行状況」等、
複数の情報をまとめて保持したい場面で、
本記事のリングバッファと同じ考え方をそのまま応用できます。
【MQL5】オブジェクトポインタとは何か|new/delete・CheckPointer・GetPointerの使い方についてわかりやすく解説してみた:まとめ
MQL5のオブジェクトポインタは、
実行中に個数が変わるオブジェクトの集まりを扱うための仕組みです。
newで生成したオブジェクトは、
deleteを呼ぶまで自分で責任を持って管理する必要があります。
CheckPointerで状態を確認してから使う、OnDeinitで残りを一括削除する、
という2つの定石を守れば、メモリリークや強制終了を避けながら安全に扱えます。
コメント