在 MQL5 中使用对象指针(基础篇)
📘

在 MQL5 中使用对象指针(基础篇)

第 1/3 篇

◍ 用对象指针接管 MT5 图形资源

在 MQL5 里画图不靠全局函数硬写,而是用对象指针去持有和管理图表上的每一个元素。把指标线、箭头、文本都当成可被引用的实例,代码结构和后期维护会清爽很多。 官方示例自 2013-09-13 发布以来累计查看 3587 次、评论 41 条,说明这套对象化写法在 MT5 开发者里长期有参考价值。 声明一个 CObject 派生类的指针后,用 new 在堆上创建实例,再通过成员函数设置坐标、颜色、样式。记得在 OnDeinitdelete 掉,否则 EA 重载会留下孤儿对象拖慢终端。

自动建对象还是用指针,差别在哪

MQL5 里写自己的类之后,变量有两种来历:直接声明类类型,系统自动把对象建好并初始化;或者先拿指针,再用 new 显式动态开对象。两者生命周期和内存归属完全不同,选错容易在 EA 重载或函数退出时踩坑。 自动对象随作用域生灭,适合只在当前函数或结构体里短暂用的轻量实例;动态对象活在那块堆内存上,除非你手动 delete,否则一直占着。外汇和贵金属杠杆高、行情跳得快,EA 频繁重算时漏删指针可能慢慢吃掉内存,这类风险得提前防。 那什么时候非用指针不可?后面会先拆几个容易写错的使用场景,再给对应改法。

「悬空指针让EA在初始化阶段直接暴毙」

在 MT5 里用对象指针,第一铁律是先 new 再调用。指针变量声明了不等于对象存在,若直接拿去访问成员,运行时触发关键性错误,MQL 程序会被终端强制卸载,不是报错继续跑,而是整个 EA 从图表消失。 上面这段示例把坑摆得很直白:全局声明了 CHello *pstatus,但故意不写 pstatus = new CHello()。OnInit() 里第一行就 Print(pstatus.GetMessage()),对象根本没分配。实盘挂 EURUSD H1 测试,日志会先显示 loaded successfully,紧接着 Initializing failed,然后 removed——从加载到移除通常在同一秒内完成。 小工程里这种错一眼能瞅出来,可当策略代码膨胀到几千行、对象生命周期跟行情结构挂钩时,悬空指针就会变成幽灵 bug。尤其当某个指针只在特定 K 线形态下才被实例化,平时不碰、偶发一碰就崩,复盘日志都难定位。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetCriticalError.mq5 |
class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "class="num">2009, MetaQuotes Software Corp."
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 一个简单的类 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHello
  {
class="kw">private:
   class="type">class="kw">string            m_message;
class="kw">public:
                     CHello(){m_message="开始...";}
   class="type">class="kw">string            GetMessage(){class="kw">return(m_message);}
  };
class=class="str">"cmt">//---
CHello *pstatus;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 调用方法显示状态
   Print(pstatus.GetMessage());
class=class="str">"cmt">//--- 如果EA被成功初始化则打印消息
   Print(__FUNCTION__," OnInit() 函数完成");
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
代码逐行拆解: class CHello 定义一个极简类,构造里把 m_message 设为“开始...”,GetMessage() 负责往外吐这个字符串。 CHello *pstatus; 只是声明了一个指针,没分配任何内存,此时 pstatus 是野指针。 OnInit() 中 Print(pstatus.GetMessage()) 直接解引用野指针——这里就是崩点,终端报关键性错误,后面那行“OnInit() 函数完成”永远打不出来。 OnTick() 空着无所谓,因为初始化阶段 EA 已经被 removed,根本轮不到报价事件。 别把正态当圣经 以为“我代码里都 new 了”就安全,也得查所有提前 return 或异常分支里有没有漏掉实例化。开 MT5 把上面代码原样编译跑一遍,亲眼看看日志里 removed 的秒级节奏,比看十篇文档都管用。外汇与贵金属杠杆交易高风险,EA 崩掉可能让你漏掉关键行情,实盘前务必在策略测试器里压边界。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetCriticalError.mq5 |
class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "class="num">2009, MetaQuotes Software Corp."
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 一个简单的类 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHello
  {
class="kw">private:
   class="type">class="kw">string            m_message;
class="kw">public:
                     CHello(){m_message="开始...";}
   class="type">class="kw">string            GetMessage(){class="kw">return(m_message);}
  };
class=class="str">"cmt">//---
CHello *pstatus;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 调用方法显示状态
   Print(pstatus.GetMessage());
class=class="str">"cmt">//--- 如果EA被成功初始化则打印消息
   Print(__FUNCTION__," OnInit() 函数完成");
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 指针上路前先过一道安检

想在 MT5 里避免 EA 因空指针直接崩掉?只要在调用对象方法前加一道指针合法性检查就够了。把检查逻辑封装进 PrintStatus,传入 CHello 对象指针后先用 CheckPointer 判断,无效就打日志,有效才取消息。 实测把示例存成 GetCriticalError_OnDemand.mq5,外部参数 GetStop 默认 false 时,日志会稳定输出『PrintStatus 变量"对象"没有被初始化!』和『OnInit 函数已完成』,EA 不会终止。把 GetStop 设 true 则跳过检查直接 pstatus.GetMessage(),此时可能触发关键性错误让程序中断。 外汇与贵金属 EA 运行环境波动大,未初始化指针是常见隐性雷。养成『传指针必查 POINTER_INVALID』的习惯,能显著降低回测与实盘切换时的非预期停止概率。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 使用 CHello 类型对象的方法打印一条消息                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," 变量 &class="macro">#x27;对象&class="macro">#x27; 没有被初始化!");
   else Print(pobject.GetMessage());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                              GetCriticalError_OnDemand.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                            [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "class="num">2009, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="kw">input class="type">bool GetStop=false;class=class="str">"cmt">// 获得关键性错误
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 一个简单的类                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHello
  {
class="kw">private:
   class="type">class="kw">string            m_message;
class="kw">public:
                     CHello(){m_message="开始...";}
   class="type">class="kw">string            GetMessage(){class="kw">return(m_message);}
   };
class=class="str">"cmt">//---
CHello *pstatus;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 使用CHello类型对象的方法打印消息                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," 变量 &class="macro">#x27;对象&class="macro">#x27; 没有被初始化!");
   else Print(pobject.GetMessage());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 调用方法显示状态
   if(GetStop)
      pstatus.GetMessage();
   else
      PrintStatus(pstatus);
class=class="str">"cmt">//--- 如果EA被成功初始化则打印一条消息

空 OnTick 与 OnInit 收尾的骨架意义

这段 MQL5 片段把 EA 的生命周期收在 OnInit 的日志输出和空 OnTick 上:Print(__FUNCTION__," OnInit() 函数完成") 只在初始化时打一行,返回 0 表示启动无误;OnTick 函数体为空,说明行情跳动时暂不执行任何逻辑。 对想验证骨架的人,直接把这段代码贴进 MT5 的 EA 编辑器编译运行,终端 Experts 标签会显示「EA名 OnInit() 函数完成」一次,之后 tick 驱动无任何动作。外汇与贵金属品种上挂这种空壳 EA 虽不交易,但能确认环境加载正常,属于零风险的结构验证。 写 EA 别急着填策略,先让 OnInit 返回 0、OnTick 留空跑通,再逐步塞入信号判断,比直接堆代码更容易排错。

MQL5 / C++
  Print(__FUNCTION__," OnInit() 函数完成");
class=class="str">"cmt">//---
  class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

很可能是悬空指针在OnInit阶段访问了未创建对象。先用指针安检确认对象是否存在再操作,避免初始化暴毙。
自动建对象每次重绘开销大且难复用;指针接管后可精准控制生命周期,减少资源泄漏和卡顿。
小布可扫描你的策略代码,标出悬空指针和未释放资源,并提示在OnInit/OnTick的收尾位置补清理逻辑。
多在OnDeinit没用指针释放对象。骨架里必须配对创建与删除,空OnTick也别漏掉收尾清理。
检查对象句柄是否有效、是否重复创建、父对象是否存活。过完再赋值指针,能挡掉大部分初始化崩溃。