16.6. Construction and Destruction
Classes that use a single new( ) method to both create and initialize objects usually don't work well under multiple inheritance. When a class hierarchy offers two or more new( ) methods (either at different inheritance levels, or in different base classes at the same level), then there is automatically a conflict of control. Only one of those new( ) methods can ultimately allocate and bless the storage for the new object, and if there is multiple inheritance anywhere in the class hierarchy you're using, the new( ) chosen may not be the new( ) you expected. Even if it is the one you wanted, any constructors on other branches of the inheritance tree will have been pre-empted and the object will not be completely initialized. Likewise, when the object's destructors are called, only one of the two or more inheritance branches can be followed during destructor look-up, so only one of the several base-class destructors will ever be called. That's particularly bad, because it's critical to call all the destructors of an inside-out object, to ensure that its attribute hashes don't leak memory (see "Destructors" in Chapter 15). For example, you could create a well-implemented inside-out class like this: package Wax::Floor; use Class::Std::Utils; { and a second class such as: package Topping::Dessert; use Class::Std::Utils; { But it's impossible to create a class that correctly inherits from both. The closest you can get is the class shown in Example 16-8. And it still fails dismally. Example 16-8. When multiple inheritance attackspackage Shimmer; use base qw( Wax::Floor Topping::Dessert ); use Class::Std::Utils; { # Attributes... my %name_of; my %patent_of; sub new { my ($class, $arg_ref) = @_; my %init = extract_initializers_from($arg_ref); # Call base-class constructor to allocate and pre-initialize... my $new_object = $class->SUPER::new($arg_ref); $name_of{ident $new_object} = $init{name}; $patent_of{ident $new_object} = $init{patent}; return $new_object; } sub DESTROY { my ($self) = @_; delete $name_of{ident $self}; delete $patent_of{ident $self}; # Call base-class destructor to continue clean-up... $self->SUPER::DESTROY( ); return; } } In the Shimmer constructor, the nested call to the ancestral constructor ($class->SUPER::new($arg_ref)) will find only the left-most ancestral new( ). So Wax::Floor::new( ) will be called, and will successfully create the object itself and initialize its "waxy" attributes. But the second inherited constructorTopping::Dessert::new( )will never be invoked, so the "edible" attributes of the object will never be initialized. Likewise, in the Shimmer class's destructor, the nested call to $self->SUPER::DESTROY( ) will be dispatched to the left-most base class only. The Wax::Floor destructor will be called, but not the destructor for Topping::Dessert. Curiously, the real problem here is not that there aren't enough constructor and destructor calls; it's that there are too many. The correct way to handle the problem is to ensure that there is only ever one call to new( ) during each construction and only one call to DESTROY( ) during each destruction. You then arrange for those single calls to correctly coordinate the initialization and cleanup for every class in the object's hierarchy. To achieve that, individual classes can no longer be responsible for their own memory allocation or object blessing, nor for their own destruction; they must not have their own new( ) or DESTROY( ) methods. Instead, they should inherit a suitable new( ) and DESTROY( ) from some standard base class. And, as every class must automatically inherit the same constructor and destructor for this scheme to work correctly, it makes sense to put that constructor and destructor in the one class that every other class automatically inherits: UNIVERSAL. The necessary code is shown in Example 16-9. Example 16-9. Implementing a universal constructor and destructor package UNIVERSAL; use List::MoreUtils qw( uniq ); The _hierarchy_of( ) subroutine traverses the inheritance tree from a given class upwards, and returns a list of the classes it inherits from. Normally, that list is sorted so that every derived class appears before any base class that it inherits from, unless the $reversed argument is true, in which case the list is sorted base-before-any-derived[*].
The UNIVERSAL::new( ) method starts out like any other constructor, creating an inside-out object in the usual way. Having created the object, new( ) then walks down through the class hierarchy from most basic to most derived (hence the 'reversed' flag on the call to _hierarchy_of( )). For each of these ancestral classes, it looks in the corresponding symbol table (*{$base_class.'::BUILD'}) to see whether that class has a BUILD( ) method defined (*{$base_class.'::BUILD'}{CODE}). If so, the universal constructor will call that method, passing it the appropriate initializer values (as recommended in the earlier "Constructor Arguments" guideline). As a result, every class everywhere inherits the same constructor, which creates an inside-out object and then calls every BUILD( ) method it can find anywhere in the class's hierarchy. Each class-specific BUILD( ) is passed the object, followed by its unique identifier (to avoid recomputing that value in every class), and finally a hash containing the appropriate constructor arguments. Similarly, the UNIVERSAL::DESTROY( ) method walks back up through the object's class hierarchy, most-derived classes first (so no 'reversed' flag). Using the same kind of symbol-table inspections as the constructor, it looks for and calls any DEMOLISH( ) method in any ancestral class, passing it the object and its unique identifying number. All of which means that no class now has to define its own constructor or destructor. Instead, they can all just define an initializer (BUILD( )) and a clean-up method (DEMOLISH( )), which will be called in the appropriate sequence, taking into account multiple inheritance relationships of the class's inheritance hierarchy. With this facility in place, you could rewrite the various wax and topping classes as shown in Example 16-10. Example 16-10. Using the universal constructor and destructor package Wax::Floor; { Then the Shimmer class could (correctly!) inherit them both like so: package Shimmer; use base qw( Wax::Floor Topping::Dessert ); { Having factored out the common construction and destruction tasks, notice how much less work is now required to implement individual classes. More importantly, the code implementing derived classes is now totally decoupled from its base classes: no more ancestral constructor calls via $class->SUPER::new( ). This approach to implementing classes is cleaner, more robust, far more scalable, and easier to maintain. It also ensures that every class is implemented in a consistent fashion, and can interoperate (under multiple inheritance) with any other class that is implemented using the same techniques. Note that it's still possible for individual classes to provide their own constructors and destructors when that's desirable, so this technique allows legacy or non-standard classes to be used as wellthough not, of course, with the same guarantees of robustness. |