继承管理:final 和 delete

MQL5 允许您对类和结构体的继承施加一些限制。

 

关键字final

通过在类名后添加 final 关键字,开发者可以禁用该类的继承。例如 (FinalDelete.mq5):

class Base
{
};
 
class Derived final : public Base
{
};
 
class Concrete : public Derived // ERROR
{
};

编译器将抛出错误“无法从 'Derived' 继承,因为它已被声明为 'final'”。

遗憾的是,对于使用此类限制的好处和场景,目前尚无共识。该关键字让类的使用者知道,由于某种原因,类的作者不建议将其作为基类(例如,其当前实现为草案,以后会发生很大变化,这可能会导致潜在的遗留项目停止编译)。

有些人试图鼓励以这种方式设计程序,即使用对象(组合)来代替继承。过度使用继承反而会增加类的耦合性(即相互影响),因为所有的子类都可以在某种程度上改变父类的数据或方法(特别是通过重定义虚函数)。因此,程序工作逻辑的复杂性和出现不可预见副作用的可能性都会增加。

使用 final 的另一个好处是编译器可以进行代码优化:对于 "final" 类型的指针,它可以用静态调度替换虚函数的动态调度。

 

关键字delete

可以在方法头中指定 delete 关键字,使其在当前类及其后代类中不可访问。父类的虚方法不能以这种方式删除(这会违反类的“契约”,即继承者将不再“是 (is a)”同一类型的代表)。

class Base
{
public:
   void method() { Print(__FUNCSIG__); }
};
 
class Derived : public Base
{
public:
   void method() = delete;
};
 
void OnStart()
{
   Base *b;
   Derived d;
   
   b = &d;
   b.method();
   
   // ERROR:   
   // attempting to reference deleted function 'void Derived::method()'
   //    function 'void Derived::method()' was explicitly deleted
   d.method();
}

尝试调用它将导致编译错误。

我们在 对象类型转换 章节中看到了类似的错误,因为编译器具有一定的智慧,可以在特定条件下“移除”方法。

建议将编译器提供隐式实现的以下方法标记为已删除:

  • 默认构造函数: Class(void) = delete;
  • 拷贝构造函数:Class(const Class &object) = delete;
  • 拷贝/赋值运算符:void operator=(const Class &object) = delete

如果您需要其中任何一个,则必须显式定义。否则,一般认为放弃隐式实现是一种良好习惯。问题是,隐式实现非常简单,并且可能会引发难以定位的问题,尤其是在强制转换对象类型时。