当对象的方法之一用作其自身属性的回调时,如何调用对象的析构函数?

Posted

技术标签:

【中文标题】当对象的方法之一用作其自身属性的回调时,如何调用对象的析构函数?【英文标题】:How do I call an object's destructor when one of its methods is used as a callback for its own property? 【发布时间】:2014-06-12 16:20:43 【问题描述】:

我正在尝试编写一个环绕 serial 端口的类来读取传感器:

classdef sensor < handle
    properties
        Value
        s
    end

    methods
         function obj = sensor(port)
             obj.s = serial(port);
             obj.s.BytesAvailableFcn = @(o,e) obj.getData;

             fopen(obj.s);
         end
         function delete(obj)
             disp('called destructor');
             try
                 fclose(obj.s);
                 delete(obj.s);
             end
          end
          function getData(obj)
              obj.Value = fscanf(obj.s, '%d');
          end
     end
end

当我尝试清除工作区时,析构函数永远不会被调用:

>> foo = sensor('COM1');
>> clear foo % should disp() something!

根据我的previousexperiences,肯定还有对foo的引用。原来这是嵌入在串口foo.s中的:​​

>> ports = instrfindall;
>> ports.BytesAvailableFcn
ans = 
    @(o,e)obj.getData

一旦我清除了BytesAvailableFcn,即

>> ports.BytesAvailableFcn = '';

然后clear all,我会显示我的called destructor

我怎样才能打破这个循环引用并让我的析构函数被调用?如果没有调用析构函数,则串行端口保持绑定。

【问题讨论】:

显而易见的解决方案是显式调用delete 方法,但我希望有更好的方法来做到这一点。 【参考方案1】:

问题不在于你有一个循环引用; MATLAB 理论上应该能赶上这些。问题是循环引用是通过 Java 传递的,而 MATLAB 无法捕获它。

更详细 - 您的 serial 对象在内部包含一个 Java 对象,它是捕获与串行端口的连接的真实对象。当您设置其回调时,MATLAB 会设置底层 Java 对象的回调。如果回调是针对包含串行对象作为属性的对象的方法,那么您有一个通过底层 Java 对象的循环引用。当您调用 clear 时,MATLAB 会检查循环引用,如果它们都在 MATLAB 中,它会捕获它们并调用析构函数 - 但它不会在通过 Java 时捕获它们。

一种解决方案是显式调用析构函数,也许这并不太麻烦。

另一种解决方法可能是简单地 将回调作为类的方法 - 也许它可能只是一个常规函数,因为它似乎并不需要任何信息来自主对象本身,而不是对串行对象的引用。如果您希望将所有代码保存在一个文件中,也许您可​​以将其创建为匿名函数(尽管要小心,因为匿名函数将捕获它创建的工作区,这取决于您创建它的位置可能包括对主对象的引用,在这种情况下,您将再次获得循环引用。

编辑:Rody 说得对 - 抱歉,我读得太快了,没有注意到 getData 实际上设置了 Value 属性,而不仅仅是返回数据。罗迪在他的回答中的解决方案可能是另一种解决方法,但就像他说的那样 - 呸。我只是手动拨打delete

【讨论】:

我看不到如何在不引用对象的情况下创建一个函数(不依赖于某些全局状态),如果您希望它设置Value 属性...调用@ 987654327@ 手动会有我的偏好。【参考方案2】:

有趣的问题! :)

我找到了一种解决方法,但它不会很漂亮。

您不能使用匿名函数。这将捕获本地工作空间,其中将包含对obj 的引用。您必须使用一级间接(到 Static 方法,否则,您会遇到同样的问题)。 此静态方法可以安全地返回函数句柄。这个函数句柄是一个函数,它必须被传递给仪器对象。 如果不传递对对象的引用,当然不可能设置Value 属性,因此,您必须在函数中创建一个全局状态、一个可变调用签名和一个Value 属性的getter .

我觉得我对此进行了过度设计(毕竟是星期五),所以如果有人看到更简单的方法,请纠正我。无论如何,这就是我的意思:

classdef sensor < handle

    properties
        s
    end        
    properties (Dependent)
        Value
    end

    methods

        function obj = sensor(port)
            obj.s = serial(port);

            % You cannot use function handle without implicitly passing obj into 
            % it. Instead, get a function handle from another function, one that 
            % does not have this problem:
            obj.s.BytesAvailableFcn = sensor.getGetData(obj.s);                

            fopen(obj.s);
        end

        function delete(obj)
            disp('called destructor');
            try %#ok<TRYNC>
                fclose(obj.s);
                delete(obj.s);
            end
        end

        % Use a getter for Value, so that whenever you query the Value property, 
        % you get the most recently read sensor data
        function V = get.Value(obj) %#ok<MANU>
            V = getData();
        end

    end

    % Use indirection to a Static method, to avoid referencing obj implicitly
    methods (Access = private, Static)
        function f = getGetData(S)
            f = @(o,e)getData(S);
        end
    end

end

% The actual sensor reader 
function data = getData(S)

    persistent port
    if nargin == 1
        port = S; return; end

    try 
        data = fscanf(port, '%d');
    catch ME
        % ...
    end

end

只是....糟糕。

【讨论】:

是的。干得好! 其实这带来了一个好处。如果我们不使用持久端口,而是使用instrfind('Type', 'serial', 'Port', nameOfPort),并传递端口名称(COM1 等)会怎样?然后,您将在类构造函数中实例化一个“匿名”serial 对象。我必须考虑一下。 这样您甚至不必将s 作为类的属性。我想知道这是否会有所帮助... 我猜持久化会有更好的性能。如果您经常阅读Value,您不想每次都通过 all 端口进行搜索...OOP 明智地让类管理串行端口是有意义的,在换句话说,将其作为财产。但是如果 MATLAB/Java 接口有问题,而 this 是解决方法,那么也许你的想法更好:)

以上是关于当对象的方法之一用作其自身属性的回调时,如何调用对象的析构函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何告诉(托管)对象通知其 KVO 需要重新缓存其属性之一?

反射的用途及实现

java回调方法钩子方法以及模板方法模式

java反射

vue双向绑定原理

Java回调机制