Приглашаем посетить
Куприн (kuprin-lit.ru)

Section 13.7.  Weakening the Argument

Previous
Table of Contents
Next

13.7. Weakening the Argument

The %REGISTRY variable also holds a reference to each animal. So even if we toss away the containing variables, for instance by letting them go out of scope:

{
  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";

we'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 we can fix this by altering the destructor:

## in Animal
sub DESTROY {
  my $self = shift;
  print '[', $self->name, " has died.]\n";
  delete $REGISTRY{$self};
}
## this code is bad (see text)

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.[*]

[*] 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[Section 13.7.  Weakening the Argument] is to use weak references . A weak reference doesn't count as far as the reference counting, um, counts. It's best illustrated by example.

[Section 13.7.  Weakening the Argument] 5.6 and later.

The weak reference mechanism is built into the core of Perl version 5.8. We need an external interface for the weaken routine, though, which can be imported from the Scalar::Util module. In Perl 5.6, we can emulate the same function using the WeakRef CPAN module. After installing this module (if needed),[*] we can update the constructor as follows:

[*] See Chapter 3 for information on installing modules.

## in Animal
use Scalar::Util qw(weaken); # in 5.8 and later
use WeakRef qw(weaken);      # in 5.6 after CPAN installation

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,[Section 13.7.  Weakening the Argument] 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.

[Section 13.7.  Weakening the Argument] 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 we'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.

We can weaken one or the other (or even both) of these links. If we weaken the link to the child, Perl can destroy the child when all other references are lost, and the parent's link simply becomes undef (or we 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, we'll simply get it as undef when the parent is no longer referenced by other data structures. It's really quite flexible.[*]

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

Without weakening, as soon as we create any parent-child relationship, 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: use weak references carefully and don't just throw them 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.


Previous
Table of Contents
Next