2

I have two classes, Plant and Generator. Generator creates a vector and broadcasts it via notify(), which Plant listens for. The classdefs are below. Note that I didn't include the actual data-generation method because it's irrelevent to my question.

classdef Plant < handle
    properties
         Listener
    end
    methods
         function ListenerCallback(obj, data)
             #% Perform an operation on data
         end
    end
end

classdef Generator < handle
    properties
        plant
    end
    events
        newSignal
    end
    methods
        function obj = Generator(plant)
            obj.plant = plant;
            obj.plant.Listener = addlistener(obj, 'newSignal', ...
                @(src, data) obj.plant.ListenerCallback(data));
        end
        function delete(obj)
            delete(obj.plant.Listener);
            disp('Generator instance deleted');
        end
    end
end

I noticed that the Generator destructor behaves really oddly: the first time I create then delete a Generator instance, it does not run the destructor until the second time I create the Generator instance. Here's an example:

>> P = Plant
P = 
  Plant handle

  Properties:
    Listener: []
  Methods, Events, Superclasses

>> G = Generator(P)
G = 
  Generator handle

  Properties:
    plant: [1x1 Plant]
  Methods, Events, Superclasses
>> clear G #% DESTRUCTOR NOT CALLED??
>> G = Generator(P)
Generator instance deleted #% why is the destructor run now?
G = 
  Generator handle

  Properties:
    plant: [1x1 Plant]
  Methods, Events, Superclasses
>> clear G
Generator instance deleted #% and why is the destructor run properly now?

It's pretty important that my destructor runs every time. What is going on here, and how can I get the destructor to operate properly? (I might just remove the listener altogether and directly call Plant.ListenerCallback() from the Generator instance if this doesn't work out.)

EDIT: Looks like when I do clear G, the variable G is removed from the workspace - but the Generator object lives on in P.Listener.Source. This is why the destructor isn't being called. So I guess there's no way to get rid of P.Listener by deleting G.. is there any way to get this to do what I want or am I just stuck?

Dang Khoa
  • 5,693
  • 8
  • 51
  • 80
  • Try `delete G; clear G` rather than `clear G` only? From the docs, "You can clear the handle of a figure or other object, but that does not remove the object itself. Use delete to remove objects and files. Deleting an object does not delete the variable, if any, used for storing its handle." – tmpearce Mar 05 '12 at 01:04
  • @tmpearce - that does work. I was hoping to use `clear G` only though since this code is part of a much larger codebase. Most people I work with who use MATLAB have no idea there is a difference between `delete` and `clear`, so this might get really confusing. – Dang Khoa Mar 05 '12 at 01:13
  • Yeah, I understand. You might want to update your question with this info, because it isn't a problem with the destructor but rather with `clear` on objects. – tmpearce Mar 05 '12 at 01:25
  • Also, check out this previous question for more info: http://stackoverflow.com/questions/7236649/matlab-run-object-destructor-when-using-clear – tmpearce Mar 05 '12 at 01:29
  • @tmpearce: it has nothing to do with handles, but rather with how handle objects (something entirely different) are handled. The object's destructor is only called once all references for the object have been eliminated. Since the listener gets overwritten when a new object is created, the class destructor is called then. – Jonas Mar 05 '12 at 02:02
  • @Jonas - do you know why the destructor is called properly the second time? – Dang Khoa Mar 05 '12 at 04:47
  • 1
    @strictlyrude27: See my edited answer below. Short answer: *facepalm* – Jonas Mar 05 '12 at 05:33

2 Answers2

1

Perhaps I'm resurrecting a 2 year old question, but...

Matlab wants to call the destructor on clear; the problem is in how you've defined your listener. You defined it as:

 obj.plant.Listener = addlistener(obj, 'newSignal', ...
                @(src, data) obj.plant.ListenerCallback(data));

In doing so, you've created an anonymous function that has a hardcoded reference to obj. When obj goes out of scope elsewhere (e.g. via clearing in the base workspace), it still continues to exist in your anonymous function. If you instead define:

 obj.plant.Listener = addlistener(obj, 'newSignal', ...
            @(src, data) src.plant.ListenerCallback(data));

there's no hardcoded references in the anonymous function. The first argument to the listener callback is always the object it was invoked from, but you get it on the fly rather than hardcoding an object reference in an anonymous function.

Hope this is still of some value to you!

Matt R
  • 11
  • 1
  • That is a nice solution for class events, but unfortunately not for timer callbacks or serial events which are independent of the class. – Jan Jul 18 '18 at 08:55
1

Why is the destructor called at such weird times?

clear G #% DESTRUCTOR NOT CALLED??

There is still a reference to G in the listener of P

G = Generator(P)
Generator instance deleted #% why is the destructor run now?

As the new Generator is instantiated, the listener gets overwritten. This calls the destructor of the first instance of Generator, since there is no longer any reference to it.

G = 
  Generator handle

  Properties:
    plant: [1x1 Plant]
  Methods, Events, Superclasses
>> clear G
Generator instance deleted #% and why is the destructor run properly now?

Let's look again what happened in the previous step: (1) The listener of plant gets overwritten with the new Generator. (2) This calls the destructor of the first Generator. (3) The destructor clears the listener of plant(!!!) (4) G in the workspace is now the last remaining instance of the new Generator. Thus, clear G calls the class destructor.


One not-pretty way that would allow you to use clear instead of delete would be to overload the clear command

function clear(varargin)

%# check for Generator objects
isGenerator = cellfun(@(x)evalin('caller','isa(x,''Generator'');'),varargin);

%# I loop here b/c I don't have the time to carefully construct
%# the evalin commands
%# Generator gets deleted, everybody else gets cleared
for i=1:nargin
   if isGenerator(i)
      evalin('caller',sprintf('delete %s',varargin{i}));
   else
      evalin('caller',sprintf('builtin(''clear'',''%s'');',varargin{i});
   end
end
Jonas
  • 74,690
  • 10
  • 137
  • 177