用伪模板替代 C++ 模板(MQL5 宏方案)
一、为什么需要模板
C++ 模板让一份算法适配多类型,避免为 int/double/string 各写一遍。MQL5 语言未实现模板机制,遇到“泛型”需求只能用变通方案。
二、C 到 C++ 的简史
C 偏系统底层,直接操作内存,缺乏数据与对象的抽象类型、多态与异常。Stroustrup 在 C 上加“类”成了 C++,模板是更晚加入的泛型扩展。理解这段历史,有助于理解 MQL5 为什么停在“类”这一层。
三、伪模板:宏替换法
用宏定义“模板参数”,在编译前做文本替换,模拟模板的一份代码多类型。下面用 startread/endread 包裹逻辑,换类型即重定义宏。
// 用宏定义“模板参数”
#define TEMPLATE_TYPE double
#define startread(arr, i) (arr[i])
#define endread
// 一份逻辑,换类型即重定义宏
TEMPLATE_TYPE sum(TEMPLATE_TYPE &a[], int n)
{
TEMPLATE_TYPE s = 0;
for(int i=0;i<n;i++) s += startread(a, i);
return s;
}
endread
四、用脚本生成多类型代码
更可控的做法是用外部脚本(如 Python)读模板、替换类型、写出多个类型特化文件。MQL5 侧用 ReadFile/Replace/WriteFile 也可在终端内做文本生成。
// 读模板、替换类型、写出具体文件
void Replace(const string tmpl, const string type, string &out)
{
out = StringReplace(tmpl, "TEMPLATE_TYPE", type);
}
void WriteFile(const string name, const string content) { /* 写盘 */ }
void ReadFile(const string name, string &content) { /* 读盘 */ }
五、取舍
六、为什么需要模板
C++ 的模板让「同一套逻辑适配多种类型」而不重复写代码。MQL5 没有原生模板,但常用宏或类继承模拟类似效果:用宏生成不同类型版本,或用基类加派生做泛型。
动机是减重复:比如一个「缓冲区管理」逻辑要对 double/int/string 各来一套,模板(或伪模板)能写成一份,编译期展开成多份,既省代码又保类型安全。
七、伪模板的实现
常见手法:用 #define 宏包一段逻辑,多次展开成不同类型版本;或用 C++ 风格的 class template 思路(若编译器支持有限模板);更稳的是用基类+派生,运行时多态替代编译期泛型。
#define DECLARE_BUF(TYPE) class CBuffer_##TYPE { TYPE m_arr[]; public: void Push(TYPE v){...} }
DECLARE_BUF(double)
DECLARE_BUF(int)
八、局限
伪模板不如原生模板安全:宏没有类型检查、出错信息晦涩,继承方案有运行时开销。所以它适合「小范围、高频复用」的辅助结构,别用来搭整个大型框架,否则后期维护成本反超省下的代码。
能用标准库现成类(如 CArray、CBuffer)就优先用,伪模板只在标准库缺位、且复用收益明显时才上。
九、何时值得上伪模板
判断标准:同一逻辑要在 3 种以上类型复用、且会长期维护,才值得写伪模板;只复用一两次,直接复制粘贴更省事、更易读。工程上的「值得」要看复用收益减维护成本。
另外,若标准库(CArrayDouble、CBuffer 等)已覆盖你的需求,优先用现成,伪模板只在标准库缺位时作为补充,避免自造轮子增加长期负担。
十、伪模板的可测试性
伪模板(宏展开)难单测,因为展开后的代码不直观。缓解办法是给每个宏生成的类写一个最小用例,确认它能正确 Push/Pop、边界不崩,至少覆盖最常见类型。
如果能用标准库现成类(CArrayDouble 等)替代伪模板,优先用——它们已经过充分测试。伪模板只在标准库缺位且复用收益明显时用,并配用例兜底,别让它成为不可测的暗角。
用伪模板替代 C++ 模板前,先确认你要的特化种类有限且可枚举,否则宏展开会失控、生成几千行难以阅读的代码。每个特化单独写测试覆盖,保证生成的代码既可读又可调试。别为了省几行重复就牺牲可维护性——模板的价值是类型安全,伪模板丢了这点就要用测试补回来。
把生成的特化代码也纳入版本管理,和手写代码同等对待,出问题能直接 git 比对定位是哪次改动引入的。伪模板再方便,也别让它游离在代码评审之外。