方法模板
不仅整个对象类型可以作为模板,其单独的方法(简单方法或静态方法)也可以作为模板。虚方法是个例外:它们不能被声明为模板。因此,模板方法不能在 接口一节中显示了相关示例。但是,接口本身可以作为模板,而虚方法可以存在于类模板中。
当方法模板包含在类/结构体模板中时,两个模板的参数必须不同。如果有多个模板方法,它们的参数之间没有任何关联,并且可以具有相同的名称。
方法模板的声明方式类似于 函数模板,但仅限于类、结构体或联合体(它们可能是也可能不是模板)的上下文中。
[ template < typename T ⌠, typename Ti ...] > ]
|
参数、返回值和方法主体可以使用类型 T(类的通用类型)和 U(方法的特定类型)。
只有在程序代码中调用特定参数组合的方法时,才会生成该方法的实例。
在上一节中,我们描述了用于存储和释放单个指针的模板类 AutoPtr。当存在多个相同类型的指针时,将它们放入容器对象中会很方便。我们创建一个具有类似功能的简单模板―SimpleArray 类 (SimpleArray.mqh)。为了避免重复控制动态内存释放的功能,我们将在类契约中声明它用于存储值和对象,而不是指针。为了存储指针,我们将它们放入 AutoPtr 对象中,然后将这些对象放入容器中。
这还有另一个好处:由于 AutoPtr 对象很小,因此易于复制(不会耗费过多资源),这在函数之间交换数据时经常发生。AutoPtr 指向的应用程序类的对象可以很大,甚至不需要在其中实现自己的拷贝构造函数。
当然,从函数返回指针的开销更小,但您需要重新设计内存释放控制的方法。因此,使用 AutoPtr 形式的现成解决方案更为简单。
对于容器内的对象,我们将创建模板类型 T 的 data 数组。
template<typename T>
|
由于容器的主要操作之一是添加元素,因此我们将提供一个辅助函数来扩展数组。
int expand()
|
我们将直接通过重载运算符 '<<' 添加元素。它使用通用模板参数 T。
public:
|
此选项通过引用获取值,即变量或对象。您现在应该注意这一点,这一点的重要性稍后就会显现。
读取元素是通过重载运算符 '[]' 来完成的(它具有最高优先级,因此表达式中不需要使用括号)。
T operator[](int i) const
|
首先,我们确保该类能够处理结构体示例。
struct Properties
|
为此,我们将在 OnStart 函数中描述一个用于该结构体的容器,并将一个对象 (TemplatesSimpleArray.mq5) 放入其中。
void OnStart()
|
通过调试日志记录,您可以验证该结构体是否位于容器中。
现在,让我们尝试在容器中存储一些数字。
SimpleArray<double> arrayNumbers;
|
遗憾的是,我们会收到“参数作为引用传递,应为变量”的错误,而这恰恰发生在重载运算符 '<<' 中。
我们需要一个通过值传递参数的重载。但是,我们不能直接写一个不包含 const 和 '&' 的类似方法:
SimpleArray *operator<<(T r)
|
如果这样做,新的变体将导致对象类型的模板无法编译:毕竟,对象只需要通过引用传递。即使该函数不用于对象,它仍然存在于类中。因此,我们将新方法定义为带有自身参数的模板。
template<typename T>
|
只有当通过值传递数据给 '<<' 运算符时,它才会出现在类中,这表明传递的绝对不是一个对象。诚然,我们无法保证 T 和 U 相同,因此会执行显式强制类型转换 (T)u。对于内置类型(如果两种类型不匹配),在某些情况下,转换可能会损失精度,但代码肯定能通过编译。唯一的例外是禁止将字符串转换为布尔类型,但该容器不太可能用于 bool 数组,因此这个限制并不重要。希望解决这个问题的人可以自行解决。
使用新的模板方法,容器 SimpleArray<double> 可以正常工作,并且不会与 SimpleArray<Properties> 冲突,因为这两个模板实例在生成的源代码中存在差异。
最后,我们检查一下包含 AutoPtr 对象的容器。为此,我们准备一个简单的 Dummy 类,它将为 AutoPtr 内部的指针“提供”对象。
class Dummy
|
在 OnStart 函数中,创建一个容器 SimpleArray<AutoPtr<Dummy>> 并填充它。
void OnStart()
|
回想一下,在 AutoPtr 中,运算符 '[]' 用于返回一个存储的指针,因此 arrayObjects[0][] 的含义是:将 data 数组的第 0 个元素返回到 SimpleArray 中,即 AutoPtr 对象,然后将第二对方括号应用于该卷,得到一个指针 Dummy*。接下来,我们可以操作该对象的所有特性:在本例中,我们获取 x 字段的当前值。
由于 Dummy 没有拷贝构造函数,因此如果不使用 AutoPtr 便无法使用容器直接存储这些对象。
// ERROR:
|
但机智的用户应该能猜到如何解决这个问题。
SimpleArray<Dummy*> bad;
|
这段代码可以编译并运行。然而,这个“解决方案”存在一个问题:SimpleArray 不知道如何控制指针,因此,当程序退出时,会检测到内存泄漏。
1 undeleted objects left
|
作为 SimpleArray 的开发者,我们有责任弥补这个漏洞。为此,我们向类中添加另一个模板方法,并重载运算符 '<<' ―这次是针对指针。由于它是一个模板,因此它只会在“按需”包含在生成的源代码中:当程序员尝试使用此重载时,即向容器写入一个指针时。否则,该方法将被忽略。
template<typename T>
|
当用指针类型实例化一个模板时,这种特化会抛出编译错误(“需要对象指针”)。因此,我们会告知用户不支持此模式。
SimpleArray<Dummy*> bad; // ERROR is generated in SimpleArray.mqh |
此外,它还会执行另一项保护措施。如果客户端类仍然具有拷贝构造函数,那么将动态分配的对象保存在容器中将不再导致内存泄漏:所传递指针 P *p 处的对象副本将保留在容器中,而原始对象将被删除。当容器在 OnStart 函数末尾被销毁时,其内部 data 数组将自动调用其元素的析构函数。
void OnStart()
|
方法模板和“简单”方法可以在主类块(或类模板)之外定义,类似于我们在 拆分类的声明和定义 一节中看到的内容。同时,它们前面都带有模板头文件 (TemplatesExtended.mq5):
template<typename T>
|
它还展示了模板化静态变量的初始化,表明采用了单例模式。
在 OnStart 函数中,创建模板实例并进行测试:
void OnStart()
|