Документация
HTML CSS PHP PERL другое

Section 13.2.  Nested Object Destruction

 
Previous
Table of Contents
Next

13.2. Nested Object Destruction

If an object holds another object (say, as an element of an array or the value of a hash element), Perl DESTROYs the containing object before any of the contained objects begin their discarding process. This is reasonable because the containing object may need to reference its contents in order to disappear gracefully. To illustrate this, let's build a "barn" and tear it down. And, just to be interesting, we'll make the barn a blessed array reference, not a hash reference.

{ package Barn;
  sub new { bless [  ], shift }
  sub add { push @{+shift}, shift }
  sub contents { @{+shift} }
  sub DESTROY {
    my $self = shift;
    print "$self is being destroyed...\n";
    for ($self->contents) {
      print '  ', $_->name, " goes homeless.\n";
    }
  }
}

Here, we're really being minimalistic in the object definition. To create a new barn, we simply bless an empty array reference into the class name passed as the first parameter. Adding an animal just pushes it to the back of the barn. Asking for the barn's contents merely dereferences the object array reference to return the contents.[Section 13.2.  Nested Object Destruction] The fun part is the destructor. Let's take the reference to ourselves, display a debugging message about the particular barn being destroyed, and then ask for the name of each inhabitant in turn. In action, this would be:

[Section 13.2.  Nested Object Destruction] Did you wonder why there's a plus sign (+) before shift in two of those subroutines? That's due to one of the quirks in Perl's syntax. If the code were simply @{shift}, because the curly braces contain nothing but a bareword, it would be interpreted as a soft reference: @{"shift"}. In Perl, the unary plus (a plus sign at the beginning of a term) is defined to do nothing (not even turning what follows into a number), just so it can distinguish cases such as this.

my $barn = Barn->new;
$barn->add(Cow->named('Bessie'));
$barn->add(Cow->named('Gwen'));
print "Burn the barn:\n";
$barn = undef;
print "End of program.\n";

This prints:

Burn the barn:
Barn=ARRAY(0x541c) is being destroyed...
  Bessie goes homeless.
  Gwen goes homeless.
[Gwen has died.]
[Bessie has died.]
End of program.

Note that Perl first destroys the barn, letting us get the name of the inhabitants cleanly. However, once the barn is gone, the inhabitants have no additional references, so they also go away, and thus Perl invokes their destructors too. Compare that with the cows having a life outside the barn:

my $barn = Barn->new;
my @cows = (Cow->named('Bessie'), Cow->named('Gwen'));
$barn->add($_) for @cows;
print "Burn the barn:\n";
$barn = undef;
print "Lose the cows:\n";
@cows = (  );
print "End of program.\n";

This produces:

Burn the barn:
Barn=ARRAY(0x541c) is being destroyed...
  Bessie goes homeless.
  Gwen goes homeless.
Lose the cows:
[Gwen has died.]
[Bessie has died.]
End of program.

The cows will now continue to live until the only other reference to the cows (from the @cows array) goes away.

The references to the cows disappear only when the barn destructor is completely finished. In some cases, we may wish instead to shoo the cows out of the barn as we notice them. In this case, it's as simple as destructively altering the barn array, rather than iterating over it.[*] Let's alter the Barn to Barn2 to illustrate this:

[*] If we're using a hash instead, we can use delete on the elements we wish to process immediately.

{ package Barn2;
  sub new { bless [  ], shift }
  sub add { push @{+shift}, shift }
  sub contents { @{+shift} }
  sub DESTROY {
    my $self = shift;
    print "$self is being destroyed...\n";
    while (@$self) {
      my $homeless = shift @$self;
      print '  ', $homeless->name, " goes homeless.\n";
    }
  }
}

Now use it in the previous scenarios:

my $barn = Barn2->new;
$barn->add(Cow->named('Bessie'));
$barn->add(Cow->named('Gwen'));
print "Burn the barn:\n";
$barn = undef;
print "End of program.\n";

This produces:

Burn the barn:
Barn2=ARRAY(0x541c) is being destroyed...
  Bessie goes homeless.
[Bessie has died.]
  Gwen goes homeless.
[Gwen has died.]
End of program.

As we can see, Bessie had no home by being booted out of the barn immediately, so she also died. (Poor Gwen suffers the same fate.) There were no references to her at that moment, even before the destructor for the barn was complete.

Thus, back to the temporary file problem. We modify our Animal class to use a temporary file by using the File::Temp module, which is part of the standard distribution. Its tempfile routine knows how to make temporary files, including where to put them and so on, so we don't have to. The tempfile function returns a filehandle and a filename, and we store both of those because we need both of them in the destructor.

## in Animal
use File::Temp qw(tempfile);

sub named {
  my $class = shift;
  my $name = shift;
  my $self = { Name => $name, Color => $class->default_color };
  ## new code here...
  my ($fh, $filename) = tempfile(  );
  $self->{temp_fh} = $fh;
  $self->{temp_filename} = $filename;
  ## .. to here
  bless $self, $class;
}

We now have a filehandle and its filename stored as instance variables of the Animal class (or any class derived from Animal). In the destructor, we close it down and delete the file:[*]

[*] As it turns out, we can tell File::Temp to do this automatically, but then we wouldn't be able to illustrate doing it manually. Doing it manually allows us to do extra processing, such as storing a summary of the information from the temporary file into a database.

sub DESTROY {
  my $self = shift;
  my $fh = $self->{temp_fh};
  close $fh;
  unlink $self->{temp_filename};
  print '[', $self->name, " has died.]\n";
}

When Perl destroys the last reference to the Animal-ish object (even if it's at the end of the program), it also automatically removes the temporary file to avoid a mess.


Previous
Table of Contents
Next
© 2000- NIV