Chapter 10. Object DestructionContents:
Nested Object Destruction In the previous two chapters, we looked at basic object creation and manipulation. In this chapter, we'll look at an equally important topic: what happens when objects go away. As you saw in Chapter 4, when the last reference to a Perl data structure goes away, Perl automatically reclaims the memory of that data structure, including destroying any links to other data. Of course, that in turn may cause other ("contained") structures to be destroyed as well. By default, objects work in this manner because objects use the same reference structure to make more complex objects. An object built of a hash reference is destroyed when the last reference to that hash goes away. If the values of the hash elements are also references, they're similarly removed, possibly causing further destruction. Suppose an object uses a temporary file to hold data that won't fit entirely in memory. The filehandle for this temporary file can be included as one of the object's instance variables. While the normal object destruction sequence will properly close the handle, you still have the temporary file on disk unless you take further action. To perform the proper cleanup operations when an object is destroyed, you need to be notified when that happens. Thankfully, Perl provides such notification upon request. You can request this notification by giving the object a DESTROY method. When the last reference to an object, say $bessie, is destroyed, Perl invokes: $bessie->DESTROY This method call is like most other method calls: Perl starts at the class of the object and works its way up the inheritance hierarchy until it finds a suitable method. However, unlike other method calls, there's no error if no suitable method is found.[43]
For example, going back to the Animal class defined in Chapter 9, you can add a DESTROY method to know when objects go away, purely for debugging purposes: ## in Animal sub DESTROY { my $self = shift; print "[", $self->name, " has died.]\n"; } Now when you create any Animal s in the program, you get notification as they leave. For example: ## include animal classes from previous chapter... sub feed_a_cow_named { my $name = shift; my $cow = Cow->named($name); $cow->eat("grass"); print "Returning from the subroutine.\n"; # $cow is destroyed here } print "Start of program.\n"; my $outer_cow = Cow->named("Bessie"); print "Now have a cow named ", $outer_cow->name, ".\n"; feed_a_cow_named("Gwen"); print "Returned from subroutine.\n"; This prints: Start of program. Now have a cow named Bessie. Gwen eats grass. Returning from the subroutine. [Gwen has died.] Returned from subroutine. [Bessie has died.] Note that Gwen is active inside the subroutine. However, as the subroutine exits, Perl notices there are no references to Gwen; Gwen's DESTROY method is then automatically invoked, printing the Gwen has died message. What happens at the end of the program? Since objects don't live beyond the end of the program, Perl makes one final pass over all remaining data and destroys it. This is true whether the data is held in lexical variables or package global variables. Because Bessie was still alive at the end of the program, she needed to be recycled, and so you get the message for Bessie after all other steps in the program are complete.[44]
10.1. Nested Object DestructionIf an object holds another object (say, as an element of an array or the value of a hash element), the containing object is DESTROY ed 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 be cleanly discarded. 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, 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 contents merely dereferences the object array reference to return the contents.[45]
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: 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 the barn is destroyed first, letting you 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 their destructors are also invoked. 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 are removed only when the barn destructor is completely finished. In some cases, you may wish instead to shoo the cows out of the barn as you notice them. In this case, it's as simple as destructively altering the barn array, rather than iterating over it.[46]
Let's alter the Barn to Barn2 to illustrate this: { 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 you 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. If you have an associated temporary file for an animal, you merely need to close it and delete the file during 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; } You now have a filehandle and its filename stored as instance variables of the Animal (or any class derived from Animal). In the destructor, close it down, and delete the file:[47]
sub DESTROY { my $self = shift; my $fh = $self->{temp_fh}; close $fh; unlink $self->{temp_filename}; print "[", $self->name, " has died.]\n"; } When the last reference to the Animal-ish object is destroyed (even at the end of the program), also automatically remove the temporary file to avoid a mess. Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|