OK, so I have an object. The object keeps a reference to another object and passes unknown methods to that object. Sounds reasonable? The AUTOLOAD looks like this:

sub AUTOLOAD { (my $sub = $The::Class::Name::AUTOLOAD) =~ s/^.*:://; my $db = shift; $db->{dbh}->$sub(@_); }
the attribute is set upon object creation and is never reset so this should work fine, yep? The inner object is implemented in XS. Still OK? Fine, so I have the object, I have tests, all tests for the XS object work both under Windows and Unix, the numerous tests for the outer one work under Windows, there's nothing OS specific as far as I can tell so everything should work fine, right?

Wrong. Under Unix all subtests pass and then the test script "creates core". Every. Single. One.

I turn the XS module's tracing on, everything looks OK. I add a plethora of debug prints into the XS modules DESTROY method, the last of them at the very last line of the function. Everything gets printed, then it crashes.

I undef the outer object at the end of a test script and put a debug print above and below. The first one is printed, the other is not. OK. So it must be something with the object destruction, but what??? There's nothing special in the outer object, no need to destroy anything explicitely, it doesn't even have it's own DESTROY method!

OK, so the XS module worked with its own (short) tests, maybe something inside got borked by the many tests of the outer one. Let's exit() the test script sooner. Crash. Let's exit() it just after the object creation. Crash! OK, OK, OK, let's undef the attribute explicitely from outside the object.

(in cleanup) Can't call method "DESTROY" on an undefined value at ... +at line ...
What??? What destroy? OK, let's see the line ...

Yes, you guessed, the line was in the AUTOLOAD method. The outer object had no DESTROY method, so it was passed to the inner one. And then was called on the already destroyed object again. Kabooooom.

Not sure why did it work under Windows, maybe thanks to a different version of Perl (5.8.6 under Unix, 5.8.8 under Windows), but ... as soon as I made sure I never redirect the DESTROY method (and added a check that the inner object is actually there before redirecting anything to it) everything works. Oh well.

Jenda
Enoch was right!
Enjoy the last years of Rome.

Replies are listed 'Best First'.
Re: Beware of object composition!
by JavaFan (Canon) on Feb 05, 2010 at 07:03 UTC
    as soon as I made sure I never redirect the DESTROY method
    Or to avoid having to make this check for each call you are delegating, just define an empty DESTROY method.

    AUTOLOAD unexpectedly handling DESTROY is a common pitfall.

Re: Beware of object composition!
by merlyn (Sage) on Feb 05, 2010 at 18:23 UTC
    use Moose; use Your::Delegated::Class; has ydc => is => 'ro', lazy_build => 1, handles => qr/.*/, isa => 'You +r::Delegated::Class'; sub _build_ydc { my $self = shift; return Your::Delegated::Class->new; }
    Moose is smart enough to delegate only the things that Your::Delegated::Class knows how to handle, minus the things defined locally. It's like AUTOLOAD delegation, done right. I used this on a recent project when what I wanted was inheritance, but had to use composition and delegation.

    -- Randal L. Schwartz, Perl hacker

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

      As if I had a chance of having Moose installed sooner than after a year of writing change requests, chasing people several levels up and around to approve them, rewriting them, rerewriting them, getting them rejected by people that write Pearl and think Moose is the animal, resubmitting, ...

      I really should get me a different job.

      Thanks anyway of course :-)

      Jenda
      Enoch was right!
      Enjoy the last years of Rome.

Re: Beware of object composition!
by shmem (Chancellor) on Feb 07, 2010 at 12:28 UTC

    It helps to read the core modules whenever one does something contained therein in a different way. The source of AutoLoader gives a clue: The DESTROY symbol is handled specially...

    AUTOLOAD { my $sub = $AUTOLOAD; my $filename = AutoLoader::find_filename( $sub ); my $save = $@; local $!; # Do not munge the value. eval { local $SIG{__DIE__}; require $filename }; if ($@) { if (substr($sub,-9) eq '::DESTROY') { no strict 'refs'; *$sub = sub {}; $@ = undef; } elsif ($@ =~ /^Can't locate/) { # The load might just have failed because the filename was + too # long for some old SVR3 systems which treat long names as + errors. # If we can successfully truncate a long name then it's wo +rth a go. # There is a slight risk that we could pick up the wrong f +ile here # but autosplit should have warned about that when splitti +ng. if ($filename =~ s/(\w{12,})\.al$/substr($1,0,11).".al"/e) +{ eval { local $SIG{__DIE__}; require $filename }; } } if ($@){ $@ =~ s/ at .*\n//; my $error = $@; require Carp; Carp::croak($error); } } $@ = $save; goto &$sub; }

    Reading that, one might conclude that object destruction could be an issue with AUTOLOAD blocks, not just dealing with composition ;-)