DekGenius.com
[ Team LiB ] Previous Section Next Section

10.6 Weakening the Argument

The %REGISTRY variable also holds a reference to each animal. So even if you toss away the containing variables:

{
  my @cows = map Cow->named($_), qw(Bessie Gwen);
  my @horses = map Horse->named($_), ("Trigger", "Mr. Ed");
  my @racehorses = RaceHorse->named("Billy Boy");
}
print "We've seen:\n", map("  $_\n", Animal->registered);
print "End of program.\n";

you'll still see the same result. The animals aren't destroyed even though none of the code is holding the animals. At first glance, it looks like you can fix this by altering the destructor:

## in Animal
sub DESTROY {
  my $self = shift;
  print "[", $self->name, " has died.]\n";
  delete $REGISTRY{$self};
}

But this still results in the same output. Why? Because the destructor isn't called until the last reference is gone, but the last reference won't be destroyed until the destructor is called.[10]

[10] We'd make a reference to chickens and eggs, but that would introduce yet another derived class to Animal.

One solution for fairly recent Perl versions[11] is to use weak references. A weak reference is a reference that doesn't count as far as the reference counting, uh, counts. It's best illustrated by example.

[11] 5.6 and later.

The weak reference mechanism is already built into the core of recent Perl versions, but as of this writing, the user interface is still accessed by a CPAN module called WeakRef. After installing this module,[12] you can update the constructor as follows:

[12] See Chapter 15 for information on installing modules.

## in Animal
use WeakRef qw(weaken); ## new

sub named {
  ref(my $class = shift) and croak "class only";
  my $name = shift;
  my $self = { Name => $name, Color => $class->default_color };
  bless $self, $class;
  $REGISTRY{$self} = $self;
  weaken($REGISTRY{$self});
  $self;
}

When Perl counts the number of active references to a thingy,[13] it won't count any that have been converted to weak references by weaken. If all ordinary references are gone, Perl deletes the thingy and turns any weak references to undef.

[13] A thingy as defined in Perl's own documentation, is anything a reference points to, such as an object. If you are an especially boring person, you could call it a referent instead.

Now you'll get the right behavior for:

my @horses = map Horse->named($_), ("Trigger", "Mr. Ed");
print "alive before block:\n", map("  $_\n", Animal->registered);
{
  my @cows = map Cow->named($_), qw(Bessie Gwen);
  my @racehorses = RaceHorse->named("Billy Boy");
  print "alive inside block:\n", map("  $_\n", Animal->registered);
}
print "alive after block:\n", map("  $_\n", Animal->registered);
print "End of program.\n";

This prints:

alive before block:
  a Horse named Trigger
  a Horse named Mr. Ed
alive inside block:
  a RaceHorse named Billy Boy
  a Cow named Gwen
  a Horse named Trigger
  a Horse named Mr. Ed
  a Cow named Bessie
[Billy Boy has died.]
[Billy Boy has gone off to the glue factory.]
[Gwen has died.]
[Bessie has died.]
alive after block:
  a Horse named Trigger
  a Horse named Mr. Ed
End of program.
[Mr. Ed has died.]
[Mr. Ed has gone off to the glue factory.]
[Trigger has died.]
[Trigger has gone off to the glue factory.]

Notice that the racehorses and cows die at the end of the block, but the ordinary horses die at the end of the program. Success!

Weak references can also solve some memory leak issues. For example, suppose an animal wanted to record its pedigree. The parents might want to hold references to all their children while each child might want to hold references to each parent.

One or the other (or even both) of these links can be weakened. If the link to the child is weakened, the child can be destroyed when all other references are lost, and the parent's link simply becomes undef (or you can set a destructor to completely remove it). However, a parent won't disappear as long as it still has offspring. Similarly, if the link to the parent is weakened, you'll simply get it as undef when the parent is no longer referenced by other data structures. It's really quite flexible.[14]

[14] When using weak references, always make sure you don't dereference a weakened reference that has turned to undef.

Without weakening, as soon as any parent-child relationship is created, both the parent and the child remain in memory until the final global destruction phase, regardless of the destruction of the other structures holding either the parent or the child.

Be aware though: weak references should be used carefully, not just thrown at a problem of circular references. If you destroy data that is held by a weak reference before its time, you may have some very confusing programming problems to solve and debug.

    [ Team LiB ] Previous Section Next Section