読者です 読者をやめる 読者になる 読者になる

__PACKAGE__を使わない方がよいとき

一時期社内でbless {}, __PACKAGE__が流行っていて、このやり方は継承した際に問題があるので使わない方がよいんじゃないですかねということで問題になるケースをあげてみる。

package Foo;
 
sub new {
    bless {}, shift;
}
 
sub call_foo {
    my $self = shift;
 
    return __PACKAGE__;
}
 
 
package Bar;
use base qw(Foo);
 
sub call_bar {
    my $self = shift;
 
    return ref $self;
}
package Hoge;
 
sub new {
    bless {}, __PACKAGE__;
}
 
sub shared_method {
    return "Hoge";
}
 
package Fuga;
use base qw(Hoge);
 
sub fuga_only_metohd {
    return "called!!";
}
 
sub shared_method {
    return "Fuga";
}
 
package main;
use strict;
use warnings;
use Test::More;
 
my $bar = Bar->new;
 
is($bar->call_foo, "Foo", "__PACKAGE__はひきづがれないこと");
is($bar->call_bar, "Bar", 'ref $selfは変わること');
 
my $fuga = Fuga->new;
 
is(ref $fuga, "Hoge", "__PACKAGE__のままになっちゃうこと");
is(ref $fuga, "Fuga", "ほんとはこうなってほしいけど、このテストは失敗する");
 
can_ok($fuga, "fuga_only_metohd", "fuga_only_metohdが呼べてほしいけど、このテストは失敗する");
 
is($fuga->shared_method, "Fuga", "Fugaになってほしいけど、このテストは失敗する");
 
done_testing;

http://gist.github.com/347660

Fugaのテストと実装を見比べたらわかるように、Hogeのnewで、bless {}, __PACKAGE__を返していると、Fuga->newも戻り値がFugaオブジェクトではなく、Hogeオブジェクトになってしまう。なので、__PACKAGE__とblessの合せ技が問題になりうることがあるということです。

Bar->call_fooを読んでいても__PACKAGE__がBarじゃなくてFooが返ってくるのは、メソッドの実行時に$class::__PACKAGE__みたいな変数へアクセスしているわけではなく、perldoc perldataには"Special Literals"と挙げられていることを考えると、コンパイル時に文字定数とかに展開されてるんじゃないかなーと思いつつ、裏は取れていない。

blessと__PACKACE__の組合せているため起きる問題なので、通常の__PACKAGE__の使用が問題ある、という話ではない。