Main Content

句柄类析构函数

基础知识

类析构函数 - 一个名为 delete 的方法,MATLAB® 在销毁句柄类的对象之前隐式调用该方法。此外,用户定义的代码可以显式调用 delete 来销毁对象。

非析构函数 - 一个名为 delete 的方法,但它不符合有效析构函数的语法要求。因此,在销毁句柄对象时,MATLAB 不会隐式调用此方法。值类中名为 delete 的方法不是析构函数。值类中名为 delete、将 HandleCompatible 属性设置为 true 的方法不是析构函数。

对象生命周期

方法特性

句柄类析构函数方法的语法

MATLAB 在销毁句柄类的对象时调用该类的析构函数。仅当 delete 被定义为具有适当语法的普通方法时,MATLAB 才会将名为 delete 的方法识别为类析构函数。

要成为有效的类析构函数,delete 方法:

  • 必须定义一个标量输入参量,它是类的对象。

  • 不能定义输出参量

  • 不能为 SealedStaticAbstract

  • 无法使用 arguments 模块进行输入参量验证。

此外,delete 方法应:

  • 引发错误,即使对象无效也是如此。

  • 为将被销毁的对象创建新句柄

  • 调用子类的方法或访问子类的属性

销毁类的对象时,MATLAB 不调用不符合条件的 delete 方法。不符合要求的 delete 方法会遮蔽 handle 类的 delete 方法,阻止对象被销毁。

由与句柄兼容的值类定义的 delete 方法不是析构函数,即便此 delete 方法由句柄子类继承也是如此。有关与句柄兼容的类的信息,请参阅Handle Compatible Classes

delete 声明为普通方法:

methods
   function delete(obj)
      % obj is always scalar
   ...
   end
end

delete 对数组进行按元素调用

MATLAB 对数组中的每个元素单独调用 delete 方法。因此,delete 方法在每次调用中只传递一个标量参量。

对已删除的句柄调用 delete 应该不会生成错误,但会不起作用。这种设计使 delete 能够处理同时包含有效和无效对象的对象数组。

delete 方法执行期间的句柄对象

对一个对象调用 delete 方法始终会导致对象销毁。当在 MATLAB 代码中显式调用 delete 时,或者当 MATLAB 调用该方法时,对象即被销毁,因为从任何工作区都无法再访问该对象。一旦调用了 delete 方法,该方法就无法中止或阻止对象被销毁。

delete 方法可以访问将被删除的对象的属性。MATLAB 直到该对象的类和所有超类的 delete 方法完成执行后才会销毁这些属性。

如果 delete 方法创建的新变量包含将被删除的对象的句柄,则这些句柄无效。在 delete 方法完成执行后,被删除对象在任何工作区内任何变量中的句柄都无效。

isvalid 方法为 delete 方法中的句柄对象返回 false,因为对象销毁在调用该方法时开始。

MATLAB 以与构造时相反的顺序调用 delete 方法。也就是说,MATLAB 先调用子类 delete 方法,再调用超类 delete 方法。

如果超类期望某个属性由子类管理,则超类不应该在其 delete 方法中访问该属性。例如,如果子类使用继承的抽象属性来存储对象句柄,则子类应在其 delete 方法中销毁该对象,但超类不应在其 delete 方法中访问该属性。

支持销毁部分构造的对象

如果在构造对象时发生错误,可能导致在对象完成创建之前调用 delete。因此,类 delete 方法必须能够处理部分构造的对象。

例如,PartialObject 类的 delete 方法在访问 Data 属性包含的数据之前,会确定该属性是否为空。如果将构造函数参量赋给 Name 属性时出错,MATLAB 会传递部分构造的对象并将其删除。

classdef PartialObject < handle
   properties
      % Restrict the Name property
      % to a cell array
      Name cell
      Data
   end
   methods
      function h = PartialObject(name)
         if nargin > 0
            h.Name = name;
            h.Data.a = rand(10,1);
         end
      end
      function delete(h)
         % Protect against accessing properties
         % of partially constructed objects
         if ~isempty(h.Data)
            t = h.Data.a;
            disp(t)
         else
            disp('Data is empty')
         end
      end
   end
end

如果使用 char 向量而不是规定的元胞数组调用构造函数,则会出现错误:

obj = PartialObject('Test')

MATLAB 将部分构造的对象传递给 delete 方法。构造函数没有设置 Data 属性的值,因为设置 Name 属性时出错。

Data is empty
Error setting 'Name' property of 'PartialObject' class:
...

何时定义析构函数方法

在 MATLAB 销毁对象之前,请使用 delete 方法执行清理操作。MATLAB 对 delete 方法的调用是可靠的,即使执行因 Ctrl-c 或错误而被中断也是如此。

如果在构造句柄类的过程中出现错误,MATLAB 将对该对象调用类析构函数,并对属性包含的所有对象以及所有初始化基类调用析构函数。

例如,假设某方法打开一个文件以用于写入,并且您要在 delete 方法中关闭该文件。delete 方法可以对该对象存储在 FileID 属性中的文件标识符调用 fclose

function delete(obj)
   fclose(obj.FileID);
end 

类层次结构中的析构函数

如果您创建类的层次结构,每个类都可以定义它自己的 delete 方法。在销毁对象时,MATLAB 调用层次结构中每个类的 delete 方法。在 handle 子类中定义 delete 方法不会覆盖 handle 类的 delete 方法。子类 delete 方法扩充超类 delete 方法。

继承 Sealed delete 方法

类无法定义密封 (Sealed) 有效析构函数。当您尝试将一个定义了 Sealed delete 方法的类实例化时,MATLAB 将返回错误。

通常,将方法声明为 Sealed 会阻止子类覆盖该方法。但是,名为 deleteSealed 方法并非有效的析构函数,因此无法阻止子类定义它自己的析构函数。

例如,如果超类定义名为 delete 的方法,该方法不是有效的析构函数,而是 Sealed,则子类:

  • 可以定义有效的析构函数(始终命名为 delete)。

  • 无法定义名为 delete 但非有效析构函数的方法。

异构层次结构中的析构函数

异构类层次结构要求异构数组必须传递给密封的方法。但是,该规则不适用于类析构函数方法。由于析构函数方法无法密封,您可以在异构层次结构中定义非密封的有效析构函数,但它确实起到析构函数的作用。

有关异构层次结构的信息,请参阅Designing Heterogeneous Class Hierarchies

对象生命周期

当对象生命周期结束时,MATLAB 调用 delete 方法。对象的生命周期在以下情况下结束:

  • 任何位置都不再引用该对象

  • 通过对句柄调用 delete 显式删除该对象

函数内部

局部变量或输入参量所引用对象的生命周期从变量赋值开始,到变量重新赋值、清除或不再于该函数或任何句柄数组中引用为止。

当显式清除变量或变量所在的函数结束时,变量会超出作用域。当变量超出作用域且其值属于定义了 delete 方法的句柄类时,MATLAB 调用该方法。MATLAB 不定义函数中变量之间的顺序。当同一个函数包含多个值时,不要假设 MATLAB 会按某个固定顺序先后销毁各个值。

句柄对象销毁期间的顺序

在销毁对象时,MATLAB 按以下顺序调用 delete 方法:

  1. 对象的类的 delete 方法

  2. 每个超类的 delete 方法,从最近的超类开始,沿层次结构向上直到最通用的超类

对于层次结构中位于同一级别的超类,MATLAB 按照类定义中指定的顺序调用其 delete 方法。例如,以下类定义先指定 supclass1,再指定 supclass2。MATLAB 先调用 supclass1delete 方法,再调用 supclass2delete 方法。

classdef myClass < supclass1 & supclass2

每次调用 delete 方法后,MATLAB 会销毁仅属于被调用方法所在类的属性值。但是,如果属性值包含仍在类作用域外引用的句柄对象,则对包含对象调用 delete 不会删除属性句柄对象本身。其他现有引用仍可以访问它们。

超类 delete 方法无法调用子类的方法或访问子类的属性。

销毁具有循环引用的对象

在一组对象中,如果有对象引用该组中的其他对象,则构成循环引用。在这种情况下,MATLAB 进行如下处理:

  • 如果对象仅在该循环内被引用,则销毁它们

  • 只要有循环外的 MATLAB 变量对循环内的任一对象进行外部引用,就不会销毁这些对象

MATLAB 按照与构造对象时相反的顺序销毁对象。有关详细信息,请参阅delete 方法执行期间的句柄对象

限制对对象 delete 方法的访问

要销毁句柄对象,可对其显式调用 delete

delete(obj)

类可以通过将其 delete 方法的 Access 属性设置为 private 来防止显式销毁对象。但是,该类的方法可以调用 private delete 方法。

如果类的 delete 方法的 Access 属性为 protected,则只有该类及其子类的方法才能显式删除该类的对象。

但是,当对象生命周期结束时,无论该方法的 Access 属性是什么,MATLAB 都会在销毁对象时调用对象的 delete 方法。

继承的 private delete 方法

类析构函数的行为不同于被覆盖方法的正常行为。MATLAB 在销毁时执行每个超类的每个 delete 方法,即使该 delete 方法不是 public 也是如此。

当您显式调用对象的 delete 方法时,MATLAB 会检查定义该对象的类中 delete 方法的 Access 属性,但不会检查该对象的超类中的相应属性。具有私有 (private) delete 方法的超类无法阻止销毁子类对象。

声明私有 delete 方法对密封类最有意义。在类未密封的情况下,子类可以将自身 delete 方法的访问权限定义为公共。显式调用公共子类的 delete 方法会导致 MATLAB 调用私有超类的 delete 方法。

非析构函数 delete 方法

类可以实现名为 delete 但非有效类析构函数的方法。MATLAB 在销毁对象时不会隐式调用此方法。在这种情况下,delete 的行为就像普通的方法。

例如,如果超类实现名为 deleteSealed 方法,但该方法不是有效的析构函数,则 MATLAB 不允许子类覆盖此方法。

由值类定义的 delete 方法不能作为类析构函数。

MATLAB 对象的外部引用

对象的生命周期如果涉及到外部语言执行自己的生命周期管理(也称为垃圾回收)时,MATLAB 将不会对其进行管理。MATLAB 无法检测何时可以安全地销毁在循环引用中使用的对象,因为外部环境在外部引用被销毁时不会通知 MATLAB。

如果无法避免对 MATLAB 对象的外部引用,则可通过在 MATLAB 中销毁对象来显式打破循环引用。

下一节说明在使用引用了 MATLAB 对象的 Java® 对象时如何管理这种情况。

Java 引用会阻止析构函数执行

Java 不支持 MATLAB 对象使用的对象析构函数。因此,对于一个同时包含 Java 和 MATLAB 对象的应用程序,管理其中所有对象的生命周期是很重要的。

如果 Java 对象引用了 MATLAB 对象,则会阻止该 MATLAB 对象被删除。在这些情况下,MATLAB 不会调用句柄对象 delete 方法,即使没有句柄变量引用该对象也是如此。为了确保您的 delete 方法得以执行,请在句柄变量超出作用域之前对此对象显式调用 delete

当您为引用 MATLAB 对象的 Java 对象定义回调时,可能会出现问题。

例如,CallbackWithJava 类创建一个 Java com.mathworks.jmi.Callback 对象,并将一个类方法赋给它作为回调函数。结果是一个 Java 对象,它通过函数句柄回调引用句柄对象。

classdef CallbackWithJava < handle
   methods
      function obj = CallbackWithJava
         jo = com.mathworks.jmi.Callback;
         set(jo,'DelayedCallback',@obj.cbFunc); % Assign method as callback
         jo.postCallback
      end
      function cbFunc(obj,varargin)
         c = class(obj);
         disp(['Java object callback on class ',c])
      end
      function delete(obj)
         c = class(obj);
         disp(['ML object destructor called for class ',c])
      end
   end
end

假设您从函数中创建 CallbackWithJava 对象:

function testDestructor
   cwj = CallbackWithJava
   ...
end

创建 CallbackWithJava 类的实例会创建 com.mathworks.jmi.Callback 对象并执行回调函数:

testDestructor
cwj = 

  CallbackWithJava with no properties.

Java object callback on class CallbackWithJava

句柄变量 cwj 只存在于函数工作区中。但是,当函数结束时,MATLAB 不调用类的 delete 方法。com.mathworks.jmi.Callback 对象仍然存在,并且引用了 CallbackWithJava 类的对象,这会阻止 MATLAB 对象被销毁。

clear classes
Warning: Objects of 'CallbackWithJava' class exist.  Cannot clear this class or
any of its superclasses. 

为避免产生无法访问的对象,请在失去 MATLAB 对象的句柄之前显式调用 delete

function testDestructor
   cwj = CallbackWithJava
   ...
   delete(cwj)
end

管理应用程序中的对象生命周期

使用 Java 或其他外部语言对象的 MATLAB 应用程序应对所涉对象的生命周期进行管理。一个典型的用户界面应用程序从 MATLAB 对象引用 Java 对象,并对引用 MATLAB 对象的 Java 对象创建回调。

您可以通过各种方式打破这些循环引用:

  • 当不再需要 MATLAB 对象时,对该对象显式调用 delete

  • 注销引用 MATLAB 对象的 Java 对象回调

  • 使用同时引用 Java 回调和 MATLAB 对象的中间句柄对象。

相关主题