[Melbourne-pm] TK repeats, scoping and keeping track of objects

Jacinta Richardson jarich at perltraining.com.au
Mon Jan 12 17:23:22 PST 2009


G'day Leigh,

Leigh Sharpe wrote:

> sub new_object
> {
>  my $object=My::Class->new();           # Create a new object to monitor
>  my $tl=$mw->Toplevel;                      # Create a top-level window.
>  $tl->title($object->{'name'});                # Set the title of the
> new window.
>  $tl->Label(-text=>"Name:")->grid(-row=>0, -column=>0); # Add Labels to
> window.
>  $tl->Label(-textvariable=>\$object->{'name'})->grid(-row=>0, -column=>1);
>  $tl->Label(-text=>"IP Address")->grid(-row=>1, -column=>0);
>  $tl->Label(-textvariable=>\$object->{'ip'})->grid(-row=>1, -column=>1);
>  $tl->Label(-text=>"Uptime:")->grid(-row=>2, -column=>0);
>  $tl->Label(-textvariable=>\$object->{'uptime'})->grid(-row=>2, -column=>1);
>  $tl->Button(-text=>"Exit",
> -command=>sub{$tl->destroy()})->grid(-row=>20, -column=>0); # Create an
> exit button.
>  $tl->repeat(5000, sub{$object->update_status()}); # Update the status
> every 5 seconds.
> }

> Now this works as it is, but I just know it won't scale well. I would
> rather have a single class method I could call which would run
> update_status() on all objects of it's class, and call it from the main
> window ($mw in this case). So my first question is, how can a class
> access all instances of itself? I considered creating an array in which
> I could store a reference to all objects, but that just seems wrong,
> 'cause it will prevent objects from going out of scope when all other
> references to them are deleted.

You can create an array of all the objects and then weaken those references so
that the object will still be cleaned up upon going out of scope.  You then want
to make sure that you handle holes in your array.  For example:

	# Weak.pm
	package Weak;
	use strict;
	use Scalar::Util qw(weaken);

	my @all_objects;

	sub new {
	        my ($class, $name) = @_;

	        my $self = { name => $name };
	        $self = bless $self, $class;

	        # Add reference to object to array
	        push @all_objects, $self;

	        # Weaken reference so that it doesn't count for garbage
	        # collection
	        weaken $all_objects[-1];

	        return $self;
	}

	sub update_all {
	        my ($class, @args) = @_;

	        foreach my $self (@all_objects) {
			# Skip if it's empty or looks funny
	                next unless ($self and ref $self eq $class);

	                print $self->{name}, "\n";
	        }
	}

	1;

	# Test script
	use strict;
	use Weak;

	my $object1 = Weak->new("obj1");
	my $object2 = Weak->new("obj2");
	{
		# Object 3 is only in this scope
	        my $object3 = Weak->new("obj3");

		# Should print out 1 - 3.
	        Weak->update_all();
	}
	# One last object
	my $object4 = Weak->new("obj4");

	print "\n\n";
	Weak->update_all();


	__END__
	obj1
	obj2
	obj3


	obj1
	obj2
	obj4


Still, I can't but think that you might be approaching the problem the wrong
way.  I'd need to know more about what you're doing in order to suggest a better
answer.

> The second question is in relation to keeping $object in scope. If I
> remove the last line of the above function, $object goes out of scope as
> soon as the function completes. Having a label with
> -textvariable=>\$object{'anything'} isn't sufficient to keep $object in
> scope.

Is there a reason you're not returning your object?  It seems odd to worry about
whether objects can properly be cleaned up when going out of scope (as you
mentioned above) and then not actually pass them back into the program so that
you can use them in the scope for which you've created them.  I haven't done a
lot of TK programming, so maybe that's where I'm missing this, but usually one
creates an object, and passes it out to the code that wanted to use it, and then
when it goes out of scope naturally it's cleaned up....

> So, if I was to create a class method as required, there would be no
> objects to call update_status() on.
> However, with the function as it is above, even destroying $tl (when the
> user clicks on the 'Exit' button) doesn't call DESTROY on $object. There
> must be some reference to the object somewhere, created by
> $tl->repeat(), which isn't going out of scope when $tl is destroy()ed.

This doesn't seem right.  In your new_object sub you create $object and it can
be accessed by that name.  After that sub has finished $object can still be
accessed by $tl's argument to repeat.  Once $tl has been cleaned up, unless it
has stored that subroutine some other way, the anonymous subroutine cannot be
accessed so it should be cleaned up and then $object cannot be accessed so it
should be cleaned up.  Add a DESTROY method to your class and see what's going
on, because I'd expect it to be cleaned up.  For example:

	sub DESTROY {
	        my ($self) = @_;
	        print $self->{name}, " is being destroyed\n";
	}

All the best,

	J

-- 
   ("`-''-/").___..--''"`-._          |  Jacinta Richardson         |
    `6_ 6  )   `-.  (     ).`-.__.`)  |  Perl Training Australia    |
    (_Y_.)'  ._   )  `._ `. ``-..-'   |      +61 3 9354 6001        |
  _..`--'_..-_/  /--'_.' ,'           | contact at perltraining.com.au |
 (il),-''  (li),'  ((!.-'             |   www.perltraining.com.au   |


More information about the Melbourne-pm mailing list