对象类型模板
对象类型模板定义以包含类型参数的头文件(参见 模板头文件一节)以及类、结构体或联合体的常用定义开头。
template <typename T [, typename Ti ...] >
|
与标准定义的唯一区别在于,模板参数可以出现在代码块中,出现在语言所有允许使用类型名称的语法结构中。
定义模板后,在代码中声明模板类型的变量时,会创建该模板的工作实例,并在尖括号中指定具体类型:
ClassName<Type1,Type2> object;
|
与调用模板函数不同,编译器无法自行推断对象模板的实际类型。
声明模板类/结构体变量并非实例化模板的唯一方法。如果模板类型用作另一个特定(非模板)类或结构体的基类型,编译器也会生成实例。
例如,以下 Worker 类即使为空,也是 Base 针对 double 类型的实现:
class Worker : Base<double>
|
此最小定义(如果 Base 类需要,还可以添加构造函数)足以开始编译和验证模板代码。
在 动态对象创建 小节中,我们了解了使用 new 运算符获取的指向对象的动态指针的概念。这种灵活的机制有一个缺点:需要监视指针,并在不再需要时“手动”删除它们。具体来说,退出函数或代码块时,必须使用 delete 调用清除所有局部指针。
为了简化此问题的解决方案,我们创建一个模板类 AutoPtr(TemplatesAutoPtr.mq5、AutoPtr.mqh)。其参数 T 用于描述 ptr 字段,该字段存储指向任意类对象的指针。我们将通过构造函数参数 (T *p) 或重载运算符 '=' 获取指针值。我们将主要工作委托给析构函数:在析构函数中,指针将与 AutoPtr 对象一起被删除(为此分配了静态辅助方法 free)。
AutoPtr 的工作原理很简单:此类的局部对象在退出描述该对象的块时将自动销毁,如果之前指示该对象“跟随”某个指针,则 AutoPtr 也会释放该指针。
template<typename T>
|
此外,AutoPtr 类实现了一个拷贝构造函数(更准确地说,是一个跳转构造函数,因为当前对象将成为指针的所有者),它允许您从函数返回一个 AutoPtr 实例以及一个受控指针。
为了测试 AutoPtr 的性能,我们将描述一个虚构的 Dummy 类。
class Dummy
|
在脚本的 OnStart 函数中,输入 AutoPtr<Dummy> 变量并从 generator 函数获取其值。在 generator 函数本身中,我们还将描述 AutoPtr<Dummy> 对象,依次创建两个动态对象 Dummy 并将其附加到 AutoPtr<Dummy> 对象(以检查是否正确释放了“旧”对象占用的内存)。
AutoPtr<Dummy> generator()
|
由于所有主要方法都记录了对象描述符(包括 AutoPtr 和受控指针 ptr),因此我们可以跟踪所有指针“转换”(为方便起见,所有行均已编号)。
01 Dummy::Dummy(int) 3145728
|
我们暂时抛开模板,详细描述一下该实用程序的工作原理,因为这样的类对许多人来说都很有用。
在 OnStart 启动后,generator 函数立即被调用。它必须返回一个值,以便在 OnStart 中初始化 AutoPtr 对象;因此,AutoPtr 的构造函数此时尚未被调用。第 02 行在 generator 函数内部创建了一个对象 AutoPtr#2097152,并获取了一个指向第一个 Dummy#3145728 的指针。接下来,创建 Dummy#4194304 的第二个实例(第 03 行),它将用 AutoPtr#2097152 中的描述符 3145728(第 04 行)替换之前的副本,并删除旧副本(第 05 行)。第 06 行创建一个临时的 AutoPtr#5242880 来返回 generator 的值,并删除本地副本 (07)。在第 08 行,OnStart 函数中 AutoPtr#1048576 对象的拷贝构造函数最终被使用,并将临时对象(在第 09 行立即被删除)的指针传递给该构造函数。接下来,我们使用指针的内容调用 Print 函数。当 OnStart 完成时,析构函数 AutoPtr (11) 将自动触发,从而使我们也删除工作对象 Dummy (12)。
模板技术使 AutoPtr 类成为一个动态分配对象的参数化管理器。但由于 AutoPtr 有一个 T *ptr 字段,它仅适用于类(更准确地说,是指向类对象的指针)。例如,尝试实例化字符串模板 (AutoPtr<string> s) 会导致模板文本中出现大量错误,这些错误表明该 string 类型不支持指针。
这并不算问题,因为此模板的用途仅限于类,但对于更通用的模板,应牢记这一细微差别(参见侧边栏)。
指针和引用
请注意,T * 结构不能出现在您计划使用的模板中,包括内置类型或结构体。重点是,MQL5 中的指针仅允许用于类。这并不是说模板理论上不能同时适用于内置类型和用户定义类型,但可能需要进行一些调整。您可能需要放弃部分功能或牺牲模板的通用性(例如,创建多个模板而不是一个、重载函数等)。
将指针类型“注入”到模板的最直接方法是在实例化模板时,在实际类型中添加修饰符 '*'(即必须与 T=Type* 匹配)。但是,某些函数(例如 CheckPointer)、运算符(例如 delete)和语法结构(例如强制转换 ((T)variable))对其自变量/操作数是否为指针非常敏感。由于这个原因,同一个模板的文本对于指针类型和简单类型的值而言,在语法上并非总是适用。
另一个需要注意的重要类型差异是:对象只能通过引用传递给方法,而简单类型的字面量(常量)不能通过引用传递。因此,编译器可能会根据推断出的 T 类型,将和号的存在与否视为错误。作为一种“解决方法”,您可以选择将自变量常量“包装”到对象或变量中。
另一个技巧是使用模板方法。我们将在下一节中看到它。
需要注意的是,面向对象技术与模式非常契合。由于指向基类的指针可用于存储派生类的对象,因此 AutoPtr 适用于任何派生 Dummy 类的对象。
理论上,这种“混合”方法广泛应用于容器类(向量、队列、映射、列表等),这些类通常是模板。容器类可能根据实现方式对模板参数施加额外要求,特别是内联类型必须具有拷贝构造函数和赋值 (copy) 运算符。
MetaTrader 5 提供的 MQL5 标准库包含许多现成的模板:Stack.mqh、Queue.mqh、HashMap.mqh、LinkedList.mqh、RedBlackTree.mqh, 等。它们都位于 MQL5/Include/Generic 目录中。没错,它们不提供对动态对象(指针)的控制。
我们将在 方法模板中我看我们简单容器类的示例。