[% setvar title Objects : Hierarchical calls to initializers and destructors %]

This file is part of the Perl 6 Archive

Note: these documents may be out of date. Do not use as reference!

To see what is currently happening visit http://www.perl6.org/

TITLE

Objects : Hierarchical calls to initializers and destructors

VERSION

  Maintainer: Damian Conway <damian@conway.org>
  Date: 1 Sep 2000
  Last Modified: 18 Sep 2000
  Mailing List: perl6-language-objects@perl.org
  Number: 189
  Version: 3
  Status: Frozen

ABSTRACT

This RFC proposes a new special method called BUILD that is invoked automagically whenever an object is created. Furthermore, it proposes that both BUILD and DESTROY methods should be invoked hierarchically in all base classes.

DESCRIPTION

One of the major limitations of object-oriented Perl is that, unlike most other OO languages, it does not automatically invoke the initializers and destructors of base classes, when initializing or destructing an object of a derived class.

This leads to tediously complex code in constructors and destructors in order to manually achieve the same effect. More often, it leads to bugs.

It is proposed that Perl 6 introduce an automatic object initialization mechanism, analogous to the automatic object clean-up mechanism provided by DESTROY methods.

It is further proposed that both the initialization and destruction mechanisms automatically call their corresponding base class versions to ensure that complete initialization and destruction of derived objects occurs correctly.

The BUILD method

It is proposed that, if a class has a method named BUILD, that method will be invoked automatically during any call to bless. It is further proposed that bless be extended to take an optional argument list after its second argument, and that this list would be passed to any BUILD method invoked by the bless.

The typical constructor would then be reduced to:

        package MyClass;
        
        sub new { bless {}, @_ }
        

with initialization handled in a separate BUILD routine:

        sub BUILD {
                my ($self, @ctor_data) = @_;
                # initialization of object referred to by $self occurs here
        }
        
        

Hierarchical BUILD calls

It is proposed that when an object is blessed, all of the BUILD methods in any of its base classes are also called, and passed the argument list appended to the invocation of bless. BUILD methods would be called in depth-first, left-most order (i.e. ancestral BUILD methods would be called before derived ones). Any given BUILD method would only be called once for the same object, no matter how many separate paths its class might be inherited through.

For example, given the following class hierarchy:

        package Base1;

        sub new { bless {}, @_ }
        sub BUILD { print "Base1::BUILD : @_\n" }


        package Base2; 

        sub BUILD { print "Base2::BUILD : @_\n" }


        package Base3; 

        sub BUILD { print "Base3::BUILD : @_\n" }


        package Derived1; 
        use base qw(Base1 Base2);

        sub BUILD { print "Derived1::BUILD : @_\n" }


        package Derived2; 
        use base qw(Base2 Base3);

        sub BUILD { print "Derived2::BUILD : @_\n" }


        package Rederived1; 
        use base qw(Derived1 Derived2);

        sub BUILD { print "Rederived1::BUILD : @_\n" }

then the call to:

        $obj = Rederived->new(1..3)

would print:

        Base1::BUILD : 1 2 3
        Base2::BUILD : 1 2 3
        Derived1::BUILD : 1 2 3
        Base3::BUILD : 1 2 3
        Derived2::BUILD : 1 2 3
        Rederived1::BUILD : 1 2 3

Note in particular that Base2::BUILD is only called once (as early as possible), even though class Rederived inherits it through two distinct paths.

Hierarchical DESTROY calls

It is further proposed that when an object's destructor is invoked, all inherited destructors would also be invoked, in depth-last, right-most order. Again, each DESTROY for an object would be called exactly once, regardless of how many different paths it is inherited through.

For example, given the following class hierarchy (with the same topology as the example for BUILD above):

        package Base1;

        sub new { bless {}, @_ }
        sub DESTROY { print "Base1::DESTROY\n" }


        package Base2; 

        sub DESTROY { print "Base2::DESTROY\n" }


        package Base3; 

        sub DESTROY { print "Base3::DESTROY\n" }


        package Derived1; 
        use base qw(Base1 Base2);

        sub DESTROY { print "Derived1::DESTROY\n" }


        package Derived2; 
        use base qw(Base2 Base3);

        sub DESTROY { print "Derived2::DESTROY\n" }


        package Rederived1; 
        use base qw(Derived1 Derived2);

        sub DESTROY { print "Rederived1::DESTROY\n" }

then the destruction of an object:

        $obj = "something else";

would print:

        Rederived1::DESTROY
        Derived2::DESTROY
        Base3::DESTROY
        Derived1::DESTROY
        Base2::DESTROY
        Base1::DESTROY

Note that Base2::DESTROY is only called once (as late as possible), even though class Rederived inherits it through two distinct paths.

Note too that the DESTROY invocation sequence is the exact reverse of the sequence for BUILD.

The REBUILD method

When a blessed object is reblessed into a different class, the REBUILD methods of the new class (and its ancestors) would be called, rather than it's BUILD methods. The REBUILDs would be called in the same "ancestor-first" sequence as for BUILD.

As well as any arguments passed to the bless, REBUILD would also be passed -- as its second argument -- the name of the previous package, so it could choose to invoke any cleanup methods from that package if it so desired. For example:

        # Full "destroy/create transfer" on reblessing...

        sub REBUILD {
                my ($self, $oldclass, @blessargs) = @_;
                $cleanup = "${oldclass}::DESTROY";
                $self->$cleanup();
                $self->BUILD(@blessargs);
        }

versus:

        # Simple "clear private data" on reblessing

        sub REBUILD {
                my ($self, $oldclass, @blessargs) = @_;
                @{$self}{grep /^_/ keys @$self} = ();
        }

Proposed standard UNIVERSAL::new

Given that most Perl classes are hash-based and that the BUILD method cleanly separate construction and initialization, it might be desirable to have the UNIVERSAL class always supply a default constructor:

        package UNIVERSAL;
        
        sub new { bless {}, @_ }
        

If this were provided, many classes could simply define an appropriate BUILD method and simply inherit their constructor.

Alternative naming schemes

During discussions on this RFC, some people indicated a preference for BLESS/REBLESS or NEW/RENEW, rather than BUILD/REBUILD.

MIGRATION ISSUES

Changes to the behaviour of DESTROY might have serious impact on code that overrides destructors in derived classes, especially if the overridden destructor delegates explicitly to its base class counterpart.

IMPLEMENTATION

Not difficult.

REFERENCES

perltoot

Conway, Object Oriented Perl, pp. 171-178.