関数の中でロット数を調整するコードを書いたのに、
呼び出し元の変数を見ると値が変わっていない。
そんな経験はありませんか。
原因の多くは、値渡しと参照渡し(&)の違いを理解していないことにあります。
この記事では、MQL4/MQL5共通の値渡し・参照渡しの仕組みを、
資金管理関数でよくある落とし穴とあわせて解説します。
・値渡しと参照渡し(&)の違い
・配列や構造体を渡すときの特別ルール
・資金管理関数のバグが起きる理由と直し方
・値渡しと参照渡しをどう使い分けるべきか
「関数で書き換えたのに元の変数が変わらない」が起きる理由
まず、このバグがなぜ起きるのかを整理します。
ロット数を減らす関数のつもりが減らなかった実例
証拠金維持率が低いときにロット数を自動で半分にする関数を書いたのに、
発注時のロットが減っていなかった、というミスは実務でよく起こります。
原因は値渡しと参照渡しの取り違え
原因の多くは、関数の引数を値渡しのまま書いてしまい、
関数内の変更が呼び出し元の変数に反映されていないことです。
この記事で扱う範囲
この記事では、値渡し・参照渡しの基本文法と、配列・構造体を渡すときの特別ルール、
資金管理関数での実装例を中心に扱います。
値渡しと参照渡し(&)の基本的な違い
MQL4/MQL5で共通する、2つの引数の渡し方を確認します。
値渡し(&を付けない場合)
void HalveLot(double lot)
{
lot = lot / 2.0;
Print("関数内のlot: ", lot);
}
&を付けない通常の引数は値渡しです。
関数が呼び出されると、引数の値がコピーされて関数内のローカル変数として扱われるため、
関数内でlotを書き換えても、呼び出し元の変数は変わりません。
参照渡し(&を付けた場合)
void HalveLotByRef(double &lot)
{
lot = lot / 2.0;
}
引数の型の後ろに&を付けると参照渡しになります。
関数にはコピーではなく変数そのものへの参照が渡されるため、関数内でlotを書き換えると、
呼び出し元の変数も変わります。
2つの違いを1つのコードで確認する
double a = 1.0;
double b = 1.0;
HalveLot(a); // aは1.0のまま
HalveLotByRef(b); // bは0.5になる
Print("a=", a, " b=", b);
同じ「半分にする」処理でも、&の有無だけで結果が変わることが分かります。
値渡しと参照渡しの仕様がこのとおりに定義されています。
・値渡し: コピーが渡る、関数内の変更は影響しない
・参照渡し: 参照が渡る、関数内の変更が反映される
・MQL5の公式用語は「値による引き渡し」「参照による引き渡し」
配列・構造体・クラスを渡すときの特別ルール
基本型(int・double等)とは別に、配列や構造体には特別な規則があります。
配列は常に参照渡しだが、&の明記が必須
void FillArray(double &arr[])
{
ArrayResize(arr, 3);
arr[0] = 1.0;
arr[1] = 2.0;
arr[2] = 3.0;
}
MQL4/MQL5では、配列・構造体型の変数・クラスオブジェクトは、
常に参照で渡される仕様です。
ただし配列の場合、関数の宣言で&を省略すると、
MetaEditorはコンパイルエラーを出します。
「常に参照渡し」という仕様があっても、
構文上は&を書く必要がある点に注意してください。
const&で読み取り専用の参照渡しにする
double SumArray(const double &arr[])
{
double total = 0;
for(int i = 0; i < ArraySize(arr); i++)
total += arr[i];
return(total);
}
const&を付けると、参照渡しによるコピー省略のメリットを得ながら、
関数内でうっかり元の配列を書き換えてしまう事故を防げます。
読み取り専用で十分な関数には、const&を使う習慣をつけておくとよいでしょう。
構造体・クラスオブジェクトも同じ考え方
構造体やクラスオブジェクトも、配列と同じく常に参照で渡される仕様です。
値としてコピーして渡したい場合は、関数側で&を付けずに宣言すれば、
MQL4/MQL5の処理系が内部でコピーを作って渡してくれます。
実際に動くコード全体(資金管理関数を値渡しから参照渡しに直す例)
証拠金維持率が低いときにロットを自動調整する関数を題材に、
値渡しの失敗例と修正版を比較します。
設計のポイント(値渡しのままでは呼び出し元に反映されない)
AdjustLotForSafety関数の中でロットを減らしても、
値渡しのままでは呼び出し元のlot変数は元の値のままになってしまいます。
参照渡しに直すことで、関数を呼び出すだけで、
呼び出し元のlotが安全な値に書き換わるようにします。
コード全文
//+------------------------------------------------------------------+
//| PassByRefLotDemo.mq4 |
//| 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 strict
input double InpMarginLevelLimit = 150.0; // この証拠金維持率%を下回ったらロットを下げる
input double InpLot = 0.10; // 基準となるロット数
// 値渡し版(失敗例): 関数内で書き換えても呼び出し元には反映されない
void AdjustLotForSafety_ValueVersion(double lot)
{
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
if(marginLevel > 0 && marginLevel < InpMarginLevelLimit)
lot = lot / 2.0;
Print("値渡し版、関数内のlot: ", lot);
}
// 参照渡し版(修正版): &を付けることで呼び出し元のlotも書き換わる
void AdjustLotForSafety(double &lot)
{
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
if(marginLevel > 0 && marginLevel < InpMarginLevelLimit)
{
lot = lot / 2.0;
Print("証拠金維持率が低いため、ロットを調整: ", lot);
}
}
void OnTick()
{
double lotValueVersion = InpLot;
AdjustLotForSafety_ValueVersion(lotValueVersion);
// ここでlotValueVersionはInpLotのまま(呼び出し元には反映されない)
double lotToUse = InpLot;
AdjustLotForSafety(lotToUse);
// ここでlotToUseは証拠金維持率次第で半分になっている
Comment(
"値渡し版の結果(変わらない): ", lotValueVersion, "\n",
"参照渡し版の結果(反映される): ", lotToUse
);
}
MetaEditorでのコンパイル・実行手順
MetaEditorで保存してコンパイルし、エラーが0件であることを確認したうえで、
デモ口座のチャートに適用し、Comment欄に表示される2つの値の違いを確認します。
値渡しと参照渡しをどう使い分けるか
どちらを選ぶべきか迷ったときの判断基準を整理します。
読み取るだけなら値渡し、またはconst&
関数の中で値を参照するだけで書き換えない場合は、値渡しを使います。
配列・構造体であれば、const&を使うことで、
誤った書き換えを構文レベルで防げます。
呼び出し元を書き換えたいなら参照渡し
ロット調整・ストップロス価格の更新など、
関数の中で計算した結果を呼び出し元の変数に反映させたい場合は、
参照渡し(&)を使います。
複数の戻り値が欲しいときにも参照渡しが使える
MQL4/MQL5の関数はreturnで1つの値しか返せないため、
複数の結果を同時に返したい場合は、
2つ目以降の値を参照渡しの引数として受け取る設計がよく使われます。
・計算した値を呼び出し元の変数へ反映したいとき
・returnで返せない複数の結果を受け取りたいとき
・大きな構造体や配列のコピーを避けたいとき
よくあるエラーと見つけ方(デバッグのコツ)
値渡し・参照渡しの取り違えで起きやすいエラーと、気づき方を整理します。
配列に&を付け忘れてコンパイルエラーになる
・配列型の引数に&を付け忘れると、MetaEditorはコンパイルエラーを出す
・「配列は常に参照で渡される」という仕様があっても、構文としての&は省略できない
・関数を修正するときは、宣言側と呼び出し側の両方で引数の型を見直す
コンパイルは通るのに値が反映されない
値渡しのまま関数を書くと、MetaEditorはエラーもワーニングも出さず、
コンパイルが通ってしまいます。
このため「コンパイルが通った=正しく動く」とは限らない点が、
このバグの見つけにくさの原因です。
Print()で関数の前後の値を比較して確認する
Print("呼び出し前: ", lot);
AdjustLotForSafety(lot);
Print("呼び出し後: ", lot);
関数を呼び出す直前と直後でPrint()を挟み、値が変わっているかどうかを確認する方法が、
このバグに気づく最も確実な手段です。
MQL4とMQL5の違い
値渡し・参照渡しに関する2言語の違いを確認します。
言語仕様自体はほぼ共通
値渡し・参照渡しの構文、配列・構造体・クラスが常に参照で渡される仕様は、
MQL4とMQL5でほぼ共通です。
MQL5の標準ライブラリでの使われ方
MQL5の標準ライブラリ(CTradeクラス等)の多くの関数は、
結果を受け取るための引数に参照渡しを使っています。
関数のシグネチャに&を見かけたら、
その引数が結果の受け皿であることを疑ってみてください。
覚えておくべき実務のポイント
MQL4/MQL5どちらでコードを書く場合でも、
「関数の中で値を変えたいなら&を付ける」という判断基準は変わりません。
よくある質問
値渡し・参照渡しについて、よくある疑問に回答します。
・&を付け忘れても基本型はコンパイルが通ってしまう
・配列だけは&を付けないとコンパイルエラーになる
・迷ったら読み取り専用はconst&、書き換えは&
&を付け忘れても動くことはありますか?
基本型(int・doubleなど)の場合、コンパイルは通ってしまいます。
ただし値渡しとして動くため、関数内の変更が呼び出し元に反映されません。
配列の場合は&を付けないとコンパイルエラーになります。
参照渡しにすればいつも安全ですか?
いいえ、安全とは限りません。
関数内で意図せず元の変数を書き換えてしまう事故につながることもあるため、
読み取り専用でよい場合はconst&を使うほうが安全です。
returnと参照渡し、どちらを優先すべきですか?
戻り値が1つで済むならreturn、
複数の結果を同時に受け取りたいなら参照渡しの引数を使う、
という使い分けが基本です。
文字列型(string)を参照渡しにする場面はありますか?
あります。
StringToTime等、文字列を解析して複数の結果を返したい関数で、
参照渡しの引数が使われることがあります。
関数の引数名に&を付けるのとポインタは同じですか?
別の仕組みです。
MQL5にはオブジェクトポインタという別の概念があります。
参照渡しの&は、あくまで関数の引数の渡し方を指定するものです。
既刊の構造体記事と何が違いますか?
既刊の構造体(struct)の記事では、
構造体を例に値渡し・参照渡しの違いに軽く触れました。
本記事は、基本型・配列・構造体/クラスのすべてを対象に、「書き換わらないバグ」
が起きる理由とデバッグ方法まで踏み込んで解説しています。
【MQL】関数の引数で値渡しと参照渡し(&の意味)を使い分ける方法について解説してみた(MQL4/MQL5共通):まとめ
値渡しは引数のコピーが渡るため、関数内の変更が呼び出し元に影響しません。
参照渡し(&)は変数そのものへの参照が渡るため、
関数内の変更が呼び出し元にも反映されます。
配列・構造体・クラスオブジェクトは常に参照で渡される仕様ですが、
配列の場合は構文として&を省略できない点に注意してください。
「関数で書き換えたのに変わらない」と感じたら、
まず引数の宣言に&が付いているかを確認してみてください。
コメント