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

Section 16.12.  Autoloading

 
Previous
Table of Contents
Next

16.12. Autoloading

Don't use AUTOLOAD( ).

Perl provides a mechanism by which you can capture and handle calls to methods that are not defined anywhere in your class hierarchy: the AUTOLOAD method.

Normally when you call a method, the interpreter starts at the class of the object on which the method was called. It then works its way upwards through the class hierarchy until it finds a package with a subroutine of the corresponding name, which it then invokes.

But if this hierarchical search fails to find a suitable method implementation anywhere in the inheritance tree, the interpreter returns to the most derived class and repeats the look-up process. On the second time through, it looks for a subroutine named AUTOLOAD( ) instead.

That means that the left-most-depth-first AUTOLOAD( ) that an object inherits will always be called to handle every unknown method call. And that's the problem. If the object's class hierarchy has two or more AUTOLOAD( ) definitions, it might be that the second one would have been the correct one to handle a particular missing method. But normally, that second one will never get the chance to do so.

There are various ways to circumvent that problem. For example, the standard NEXT module can be used to reject a particular AUTOLOAD( ) invocation and resume the original method look-up; or under Class::Std you can declare each AUTOLOAD( ) to be :CUMULATIVE and make sure only one of them ever returns a value; or you can dispense with AUTOLOAD( ) entirely and use Class::Std's AUTOMETHOD( ) mechanism instead[*].

[*] For more details on these alternatives, with full examples, see the documentation of the NEXT and Class::Std modules.

However, none of these solutions uses the standard Perl AUTOLOAD( ) semantics, so all of them will be harder to maintain. And the first two suggestions also require additional vigilance to get right: either making certain that every AUTOLOAD( ) redispatches on failure via a call to $self->NEXT::AUTOLOAD( ); or ensuring that every AUTOLOAD( ) is marked :CUMULATIVE and that they're all mutually exclusive. So neither of them will be as robust as normal methods.

More importantly, a class that autoloads undefined methods effectively has an interface of unlimited size and complexity. That in itself is reason enough not to use the mechanism. But, worse still, the overwhelming majority of that interface will then rely on a single AUTOLOAD( ) subroutine. Such a subroutine is correspondingly much harder to write, as it has to identify all the method calls it can handle, then handle those calls correctly, whilst cleanly and accurately rejecting cases it can't handle. As a result, AUTOLOAD( ) methods tend to be large, complex, slow, and riddled with difficult corner cases.

By far the commonest mistakes are to forget to provide an explicit DESTROY( ) method for any class with an AUTOLOAD( ) or, alternatively, to forget to tell AUTOLOAD( ) how to handle a DESTROY( ) request. Either way, if there is no explicit destructor, every time an object of the class is destroyed, the AUTOLOAD( ) is called instead, usually with either comic or tragic results.

AUTOLOAD( ) doesn't promote efficiency, conciseness, robustness, or maintainability, and is best avoided entirely. Providing an arbitrary number of methods via autoloading can sometimes appear to be the right solution:

    package Phonebook;
    use Class::Std;
    use Carp;
    {
        my %entries_of : ATTR;

        # Any method call is someone's name: store their phone number or get it...
        sub AUTOLOAD {
            my ($self, $number) = @_;

            # Extract get/set mode and person's name from method name...
            our $AUTOLOAD;
            my ($mode, $name) = $AUTOLOAD =~ m/.* :: ([gs]et)_(.*)/xms
                or croak "Can't call $AUTOLOAD on object";

            # Update if it's a set_<name> operation...
            if ($mode eq 'set') {
                croak "Missing argument for set_$name" if @_ == 1;
                $entries_of{ident $self}->{$name} = $number;
            }

            return $entries_of{ident $self}->{$name};
        }
    }

    # and later...

    my $lbb = Phonebook->new( );

    $lbb->set_Jenny(867_5309);
    $lbb->set_Glenn(736_5000);

    print $lbb->get_Jenny( ), "\n";
    print $lbb->get_Glenn( ), "\n";

However, it's almost always cleaner, more maintainable, and more flexible to define a fixed number of predefined methods to provide the necessary functionality, passing them an extra argument that specifies the particular arbitrary behaviour desired. For example:


    package Phonebook;
    use Class::Std;
    use Carp;
    {
        my %entries_of : ATTR;

        
# Set numbers...
sub set_number_of { croak 'Missing argument for set_number_of( )' if @_ < 3; my ($self, $name, $number) = @_; $entries_of{ident $self}->{$name} = $number; return; }
# Get numbers...
sub get_number_of { croak 'Missing argument for get_number_of( )' if @_ < 2; my ($self, $name) = @_; return $entries_of{ident $self}->{$name}; } }
# and later...
my $lbb = Phonebook->new( ); $lbb->set_number_of(Jenny => 867_5309); $lbb->set_number_of(Glenn => 736_5000); print $lbb->get_number_of('Jenny'), "\n"; print $lbb->get_number_of('Glenn'), "\n";

If autoloading still seems like the right solution, then consider using the AUTOMETHOD( ) mechanism provided by Class::Std instead of Perl's standard AUTOLOAD( ). An AUTOMETHOD( ) is expected to return either a handler subroutine that implements the requested method functionality, or else an undef to indicate that it doesn't know how to handle the request. Class::Std then coordinates every AUTOMETHOD( ) in an object's hierarchy, trying each one in turn until one of them produces a suitable handler.

The advantage of this approach is that the first AUTOMETHOD( ) that's invoked doesn't have to disenfranchise every other AUTOMETHOD( ) in the hierarchy. If the first one can't handle a particular method call, it simply declines it and Class::Std tries the next candidate instead.

For example, the AUTOLOAD'd version of the Phonebook class could be made cleaner, more robust, and less disruptive in class hierarchies by rewriting it like so:


package Phonebook;
use Class::Std;
{
    my %entries_of : ATTR;

    
# Any method call is someone's name: store their phone number or get it...
sub AUTOMETHOD { my ($self, $ident, $number) = @_; my $subname = $_;
# Requested subroutine name is passed via $_

        # Return failure if not a get_<name> or set_<name>

        # (Next AUTOMETHOD( ) in hierarchy will then be tried instead)...
my ($mode, $name) = $subname =~ m/\A ([gs]et)_(.*) \z/xms or return;
# If get_<name>, return a handler that just returns the old number...
return sub { return $entries_of{$ident}->{$name}; } if $mode eq 'get';

        # Otherwise, set_<name>, so return a handler that updates the entry

        # and then returns the old number...
return sub { $entries_of{$ident}->{$name} = $number; return; }; } }
# and later...
my $lbb = Phonebook->new( ); $lbb->set_Jenny(867_5309); $lbb->set_Glenn(736_5000); print $lbb->get_Jenny( ), "\n"; print $lbb->get_Glenn( ), "\n";

    Previous
    Table of Contents
    Next
    © 2000- NIV