overloadと再blessの問題

overload が無効? - Practice of Programming

軽く調べてみたんですが、どうやらオブジェクトを再blessした場合、overloadフラグが立たないケースがあるようです。

具体的に言うと、overloadを使用しないオブジェクト$mが、overloadを使用しているクラスに再blessした場合、$mにはoverloadフラグが立つが$mをコピーした別のリファレンスにはoverloadフラグが立たないということです。

あー、ややこしい。こーゆーのは文章で説明するよりもコードを見た方がわかりやすいってことで以下コードで説明。

まずoverloadフラグが立つケース

use Mortal qw/:all/;
use strict;

my $m = bless {}, "main";

bless $m , 'Mortal';

print $m; # This object has already been dead.

で、次が立たないケース

use Mortal qw/:all/;
use strict;

my $m = bless {}, "main";

my $m_copy = $m; # コピー

bless $m , 'Mortal'; # $mにフラグが立つが、$m_copyにはフラグが立たない

print $m_copy; # Mortal=HASH(0x814ccc0)
print $m;      # This object has already been dead.

ということです。

つまりmortal関数がうまくいって、_mortal2,_mortal3関数がうまく行かないのは後者がコピーされたリファレンスだったからです。

sub mortal {
    bless shift , __PACKAGE__; # 呼び出し元のオブジェクトにフラグが立つ
}

sub _mortal2 {
    my $self = shift;          # コピー発生
    bless $self , __PACKAGE__; # $selfにはフラグが立つが呼び出し元のオブジェクトにはフラグが立たない
}

sub _mortal3 {
    my $self = $_[0];          # コピー発生
    bless $self , __PACKAGE__; # $selfにはフラグが立つが呼び出し元のオブジェクトにはフラグが立たない
}

あと元記事の例で、DateTimeがうまく動いていたのはDateTimeはそもそもoverloadを使用しているクラスなので始めにオブジェクト作った時点でoverloadフラグが立っているのでコピーしてもoverloadフラグは問題なく引き継がれていたというわけです。

この現象からoverloadフラグはコピーでは引き継がれるが、再bless時は引き継がれないということになります。これは問題ですね。

で、もうちょっと調べてたらこんなのを発見しました。

オーバーロードと再bless

リファレンスが他のクラスへと再blessされてもオーバードーロは動作するようになりました. 内部的にはこれは"オーバーロードされている" というフラグがリファレンスから論理的に常に存在しているべき場所であるその参照先へと移動することで実装されています. (Nicholas Clark)

perldelta - perl v5.9.4 更新情報

# オーバードーロてww

perl5.9.4ではこの問題は修正されているようですね。良かった良かった。

でも現状の対策としてはどうすればいいのかちょっと思いつかないですね。リファレンスを捜索してoverloadフラグを立てる、なんてことをするモジュールとか作れるのかしら・・・。