Find where handle is stored in scope

2020-07-03 06:29发布

MATLAB handle class objects are deleted when they go out of scope. I have objects that can be reused in different parts of an application, but which I want to destroy when they are no longer used anywhere. MATLAB's built in lifecycle behaviour allows me to do this without maintaining any additional global list to keep track of what might be using that object.

However I have a situation where an object I think should have gone out of scope is still firing event listener callbacks that are deleted as part of the object's destructor. I know where I think the last handle to this object in existence should have been stored, and sure enough when I check there that handle has been cleared. So there must be instances of this handle in scope somewhere else.

My application is a complex network of objects stored as properties of other objects. Is there anything I can do to help track down where in scope the handle to this object is being stored?

Example

First set up a handle class with an event to listen to:

classdef Yard < handle
    events
        RollCall
    end
end

Then a handle class that reacts to RollCall events from a Yard object by displaying some text and then notifying its own event:

classdef Kennel < handle
    properties
        id
        yardListener
    end

    events
        RollCall
    end

    methods
        function obj = Kennel(yard,id)
            obj.yardListener = event.listener(yard,'RollCall',@obj.Report);
            obj.id = id;
        end

        function Report(obj,~,~)
            fprintf('Kennel %d is in the yard\n', obj.id);
            notify(obj,'RollCall');
        end
    end
end

And finally a class that reacts to RollCall events from a Kennel object by displaying some text:

classdef Dog
    properties
        name
        kennel
        kennelListener
    end

    methods
        function obj = Dog(name,kennel)
            obj.name = name;
            obj.kennel = kennel;
            obj.kennelListener = event.listener(kennel,'RollCall',@obj.Report);
        end

        function Report(obj,kennel,~)
            fprintf('%s is in kennel %d\n', obj.name, kennel.id);
        end
    end 
end

Now add some instances of these classes to the workspace:

Y = Yard;
% Construct two Dog objects, in each case constructing a new Kennel object to pass to the constructor
dogs = [Dog('Fido',Kennel(Y,1)) Dog('Rex',Kennel(Y,2))];
% Construct a third Dog, reusing the Kennel object assigned to dog(1)
dogs(3) = Dog('Rover',dogs(1).kennel);

I now have two Kennel objects in scope, with handles referenced in the properties of the Dog objects in the array dogs. Calling notify(Y,'RollCall') produces the following output:

Kennel 2 is in the yard
Rex is in kennel 2
Kennel 1 is in the yard
Rover is in kennel 1
Fido is in kennel 1

If the original two Dogs are deleted then kennel 2 goes out of scope but kennel 1 remains active since it is still referenced by the remaining Dog:

>> dogs = dogs(3);
>> notify(Y,'RollCall')
Kennel 1 is in the yard
Rover is in kennel 1

However if I hide an additional handle to kennel 1 somewhere else in scope before deleting the remaining Dog then it will remain active:

>> t = timer('UserData',dogs(1).kennel);
>> clear dogs
>> notify(Y,'RollCall')
Kennel 1 is in the yard

The question is if I don't know where or when this extra reference was created and why it hasn't been deleted, what can I do to debug the existence of the object?

标签: matlab oop
1条回答
家丑人穷心不美
2楼-- · 2020-07-03 06:58

This is something I have dealt with a lot. You see, clearing the handle object variable in one scope will not trigger the destructor if there is still a reference to the object in any other scope. If you explicitly called the destructor for the Dog and Kennel objects, your timer object would have a reference to an invalid object.

In the constructor for Dog there is an event listener created, whose destructor is never called which retains a reference to an instance of Dog.

I would implement a delete function for Dog and Kennel which called the delete function for the listeners. Here is a coding pattern I use a lot.

classdef MyClass < handle
   properties 
      listener % listeners are themselves handle objects
   end
   methods
      %% Class constructor creates a listener 
      %% Class methods
      function delete(obj)
         % do this for all handle object properties
         % Also, avoid calling constructors for handle objects in
         % the properties block. That can create references to handle
         % objects which can only be cleared by a restart
         if ishandle(obj.listener)
            delete(obj.listener)
         end
      end
   end
end

In the example you gave, you created a timer object which also maintains a reference to your dogs object. Clearing dogs does not eliminate this reference. Neither would clearing the timer object. You must explicitly delete timer objects.

I use handle objects a lot, and usually design GUIs to go with them (aside, never, never, never use GUIDE for this. It is the worst thing ever). You have to be sure to clear all references to anyhandleobjects called in your code. I do this by setting the'DeleteFcn'` callback on the graphics handle object that contains the GUI (could be a figure, uicontainer, uipanel, etc.). I include this example because it illustrates how carefully references to handle objects need to be maintained.

function dispGUI(handleOBJ,hg)

  %% build gui in hg
  set(hg,'DeleteFcn',@deleteCallBack)
  h  = uicontrol(hg,'Callback',@myCallback)
  function myCallback(h,varargin)
     % So now there is a reference to handleOBJ
     % It will persist forever, even if cleared in the caller's
     % workspace, thus we clear it when the containing graphics 
     % object delete callback is triggered.
     if isvalid(handleOBJ)
        set(h,'string','valid')
     else
        set(h,'string','invalid')
     end
  end


  function deleteCallBack(varargin)
     % calling clear on handleOBJ clears it from the scope of the 
     % GUI function
     % also call clear on the containing function
     clear handleOBJ
     clear(mfilename)
  end

end

Another issue I have noticed with handle class objects, is that if you modify and save their classdef file while there are live references to these objects you can occasionally get into a situation where there are references to the object that you can only clear by resetting Matlab.

查看更多
登录 后发表回答