perlobj - Perl のオブジェクト
まず最初に、あなたが Perl におけるリファレンスがなんであるかを理解している 必要があります。 perlref を参照してください。 第二に、これから説明するリファレンスの動作が複雑すぎると思うようなら、 Perl におけるオブジェクト指向プログラミングのチュートリアルが perltoot と perltooc にあります。
ここまででまだ読み進める気があるのなら、あなたを安心させるだろう 三つの非常に単純な定義があります。
オブジェクトとは、単に自分がどのクラスに属しているのかを 知っているようなリファレンスです。
クラスとは、単にオブジェクトのリファレンスを取り扱うメソッドを 提供するパッケージです。
メソッドとは、単にその第一引数にオブジェクトのリファレンス (もしくはクラスのメソッドに対するパッケージ名)を取るような サブルーチンです。
これらのポイントを、より深く掘り下げて行きます。
C++ とは違って、Perl はコンストラクタに対して特別な構文を用意していません。 コンストラクタは単に、クラスに "bless" したなにかのリファレンスを 返すようなサブルーチンで、一般的にはサブルーチンが定義されているクラスです。 以下に典型的なコンストラクタを示します。
package Critter; sub new { bless {} }
new
という語は特別なものではありません。
コンストラクタを以下のように記述することもできます:
package Critter; sub spawn { bless {} }
実際のところ、これは C++ プログラマーが C++ と同じように
Perl の new
が動くと考えて落とし穴にはまることがないという点で
好ましいものではあります。
あなたには、あなたが解決しようとしている問題に関するコンテキスト
でわかりやすいコンストラクタにすることをお奨めします。
例を挙げると、Perl の Tk エクステンションのコンストラクタでは、
作成するウィジェットから名前を取っています。
Perl のコンストラクタに関して C++ と違うことは、
C++ では自分でメモリを割り当てる必要があるということです。
{}
は空の無名ハッシュを割り当てます。
bless()
は引数にリファレンスを取り、そのオブジェクトにオブジェクトが
Critter のリファレンスであることを教え、リファレンスを返します。
参照されているオブジェクトはそれ自身自分が bless されていることを知っていて、
リファレンスは以下の例のように直接返すことができます。
sub new { my $self = {}; bless $self; return $self; }
実際のところ、構築の一部としてクラスのメソッドを呼び出すようなもっと 複雑なコンストラクタを良く見かけることでしょう。
sub new { my $self = {}; bless $self; $self->initialize(); return $self; }
継承について心配していて(そうあるべきなのですが。 perlmodlib/"Modules: Creation, Use, and Abuse"を参照してください)、 継承されているかもしれないコンストラクタで引数二つを取る形式の bless を 使いたいのであれば:
sub new { my $class = shift; my $self = {}; bless $self, $class; $self->initialize(); return $self; }
あるいは、ユーザーが CLASS->new()
ではなく $obj->new()
を使うことを期待しているのであれば、以下のような形式を使います。
(インスタンスに対して new()
を呼び出すのにこれを使っても、自動的なコピーは
一切行われないことに注意してください。
もしオブジェクトに対して浅い、あるいは深いコピーを望むなら、それが
できるように特に処理しなければならないでしょう。)
initialize()
というメソッドは $class を私たちがオブジェクトに
bless しているかどうかに関らず使われます。
sub new { my $this = shift; my $class = ref($this) || $this; my $self = {}; bless $self, $class; $self->initialize(); return $self; }
クラスパッケージの中では、メソッドはリファレンスを普通のリファレンスとして 扱うでしょう。 クラスパッケージの外側では、リファレンスは一般的にクラスメソッドを 通してのみアクセスすることのできる不透明な値(opaque value)であるかのように 扱われます。
コンストラクタは現在参照されているオブジェクトを別のクラスに 属させるために、再 bless するかもしれません; これはほとんど確実にトラブルに巻き込まれます。 しかし、その新しいクラスは、後で掃除する原因となります。 オブジェクトは一度に一つのクラスにしか属することができないかのように 以前の bless は忘れ去られます(多くのクラスからメソッドを継承することが 自由にできるとしても、です)。 もし再ブレスしなければならないというのなら、その親クラスは不正な 振る舞いをしています。
解説: Perl のオブジェクトは bless されています。
リファレンスはそうではありません。
オブジェクトは自分が属しているパッケージがなんであるか知っていますが、
リファレンスは知りません。
関数 bless()
はリファレンスをオブジェクトとみなすために使われます。
以下の例を見てください。
$a = {}; $b = $a; bless $a, BLAH; print "\$b is a ", ref($b), "\n";
これは "$b as being a BLAH" と表示されます; これで明らかなように、bless() はリファレンスに対してではなく オブジェクトに作用します。
C++ とは異なり、Perl はクラス定義に対する特別な構文を用意してはいません。 パッケージを、メソッド定義を押し込められたクラスとして使います。
各パッケージには、カレントのパッケージでメソッドを見つけられなかったときに メソッドを探しに行く別のパッケージを指示する @ISA と呼ばれる 特殊な配列があります。 これが、Perl が継承を実装しているやり方です。 配列 @ISA の各要素は、クラスパッケージである別のパッケージの名前です。 見つからないメソッドは、デフォルトでは深さ優先、左から右の順序でクラスを 探します。 (その他の検索順と、その他のより深い情報については mro を 参照してください)。 @ISA を通じてでアクセスすることのできるクラスは、カレントクラスの 基底クラスとして知られています。
全てのクラスは暗黙のうちにその基底クラスとして UNIVERSAL
という
クラスを継承しています。
UNIVERSAL クラスで自動的に提供される一般的に使われるメソッドが
いくつかあります。
詳しくは デフォルトの UNIVERSAL メソッド または UNIVERSAL を
参照してください。
基底クラスで探しているメソッドが見つかれば、効率のために そのメソッドはカレントクラスでキャッシングされます。 @ISA の変更や新たなサブルーチンの定義はキャッシュを無効化し、Perl に再度の 走査を行わせます。
カレントクラス、名前つきの基底クラス、UNIVERSAL クラスを検索して、
これらのいずれもが要求されたメソッドを持っていなければ、
AUTOLOAD()
という名前のメソッドを検索します。
AUTOLOAD が見つかれば、このメソッドは見失ったメソッドの途中で
呼び出され、パッケージグローバルの $AUTOLOAD をメソッドの完全修飾名に
なるように設定します。
ここまで全部失敗したならば、Perl は音を上げてエラーメッセージを出します。
AUTOLOAD の継承を止めたい場合は、単に以下のようにすると:
sub AUTOLOAD;
呼び出しは予備されたサブルーチンの名前を使って die します。
Perl のクラスはメソッドの継承のみを行います。 データの継承はクラス自身にまかされています。 このことは Perl の問題ではありません。 なぜなら、大部分のクラスは無名ハッシュを使って、クラスのオブジェクトの 属性をモデル化しています。 この無名ハッシュはそのオブジェクトに対して何事かを行うような 様々なクラス毎に固有の名前空間を小さくきり分ける役割を果たします。 これに関する問題はあなたが使ったハッシュのひとかけらが 既に使われたものではないということに確信が持てないことです。 これに関する現実的な対応策は、ハッシュのフィールド名に パッケージ名を使うというものです。
sub bump { my $self = shift; $self->{ __PACKAGE__ . ".count"}++; }
C++ とは異なり、Perl はメソッド定義に対する特別な構文を持っていません (しかしながら、後述するようにメソッドの起動のためにはちょっとした 構文を提供しています)。 メソッドはその第一引数として、そのメソッドを起動したオブジェクト (リファレンス)、もしくはパッケージ(文字列)を期待しています。 メソッドの呼び出し方にはには二種類あり、 それぞれ私たちがクラスメソッド、インスタンスメソッドと呼ぶものです。
クラスメソッドは、その第一引数としてクラスの名前を期待します。 クラスメソッドはクラス全体に対する機能を提供するものであって、クラスに 属する個々のオブジェクトに対するものではありません。 コンストラクタは普通はクラスメソッドですが、 代替案については perltoot と perltooc を参照してください。 多くのクラスメソッドは単純にその第一引数を無視します。 これは、既に自分が置かれているパッケージの名前を知っているし、 パッケージがどのような経路で起動されたのかを 気にする必要がないからです。 (通常のインスタンスメソッドと同じようにクラスメソッドは継承木に 従っているので、これらが同一である必要はありません)クラスメソッドの もう一つの典型的な使用例は名前によってオブジェクトを 検査するためのものです。
sub find { my ($class, $name) = @_; $objtable{$name}; }
インスタンスメソッドは、その第一引数としてオブジェクトのリファレンスを 期待します。 典型的には、第一引数は "self" とか "this" といった変数に shift され、 以後は通常のリファレンスのように扱います。
sub display { my $self = shift; my @keys = @_ ? @_ : sort keys %$self; foreach $key (@keys) { print "\t$key => $self->{$key}\n"; } }
歴史的、あるいはその他の様々な理由によって、Perl はメソッド呼び出しを 書くための、二つの等価な方法を提供しています。 より単純でより一般的な方法は矢印記法を使うことです:
my $fred = Critter->find("Fred"); $fred->display("Height", "Weight");
リファレンスに対する ->
演算子の使用については既に
親しんでいることでしょう。
実際のところ、上述の $fred
はオブジェクトへのリファレンスなので、
メソッド呼び出しを、単にデリファレンスの別の形と考えることができます。
矢印の左側に何があるか、リファレンスかクラス名か、は最初の引数として メソッドサブルーチンに渡されます。 従って、上述のコードは以下のものとほとんど等価です:
my $fred = Critter::find("Critter", "Fred"); Critter::display($fred, "Height", "Weight");
サブルーチンがどのパッケージにあるかを Perl はどうやって知るのでしょうか? 矢印の左側を見ます; これはパッケージ名かオブジェクトへのリファレンス (つまりパッケージに bless された何か) のどちらかである必要があります。 どちらの場合も、それが Perl が探し始めるパッケージです。 もしそのパッケージに指定された名前のサブルーチンがないなら、Perl は そのパッケージの基底クラスを探し始めます; それを繰り返します。
もし必要なら、Perl に他のパッケージを見るように強制することも 可能です。
my $barney = MyCritter->Critter::find("Barney"); $barney->Critter::display("Height", "Weight");
ここで MyCritter
は、おそらく自分自身の find()
と display()
を
定義しているCritter
のサブクラスです。
これらのメソッドが何をしているかは指定していませんが、
これらのサブルーチンを Critter
から探すように Perl に強制しているので、
それは問題になりません。
上記の特別な場合として、現在のクラスの @ISA
リストにあるパッケージから
メソッドを探し始めるように Perl に指示する、SUPER
擬似クラスもあります。
package MyCritter; use base 'Critter'; # sets @MyCritter::ISA = ('Critter');
sub display { my ($self, @args) = @_; $self->SUPER::display("Name", @args); }
SUPER
は、オブジェクトのスーパークラスではなく、カレントパッケージ の
スーパークラスを参照するということに注意することは重要です。
また SUPER
擬似クラスは現在のところメソッド名への修飾子としてのみ
使え、普通に使われるクラス名のその他の方法は使えません。
つまり:
something->SUPER::method(...); # OK SUPER::method(...); # WRONG SUPER->method(...); # WRONG
クラス名やオブジェクトリファレンスの代わりに、矢印の左側に 置けるもののどちらかを返す任意の式も使えます。 従って、以下の文は有効です:
Critter->find("Fred")->display("Height", "Weight");
そして以下のようにします:
my $fred = (reverse "rettirC")->find(reverse "derF");
矢印の右側は典型的にはメソッド名ですが、メソッド名やサブルーチン リファレンスが入った単純スカラ変数も使えます。
矢印の右側がサブルーチンへのリファレンスを含むスカラのとき、 これは最初の引数として矢印の左側のクラス名やオブジェクトを指定して リファレンスされたサブルーチンを直接呼び出すのと等価です。 検索は行われず、サブルーチンは矢印の左側のクラス名やオブジェクトに関係する パッケージに定義されている必要はありません。
例えば、以下の $display の呼び出しは等価です:
my $display = sub { my $self = shift; ... }; $fred->$display("Height", "Weight"); $display->($fred, "Height", "Weight");
メソッドを起動する他の方法は、「間接オブジェクト」記法と呼ばれる方法を 使うものです。 この文法は、オブジェクトが導入されるよりずっと以前の Perl 4 から 利用可能で、以下のように、ファイルハンドルで今でも使われています:
print STDERR "help!!!\n";
これと同じ構文を、クラスメソッドやインスタンスメソッドを呼び出すときに 使うことができます。
my $fred = find Critter "Fred"; display $fred "Height", "Weight";
オブジェクトやクラス名とパラメータの間にカンマがないことに 注目してください。 これによって、通常のサブルーチン呼び出しではなく間接メソッド呼び出しを しようとしていることを Perl に伝えています。
しかし、引数がなかったら? この場合、Perl は求められているものを推測しなければなりません。 さらに悪いことに、この推測は コンパイル時 に行わなければなりません。 Perl は通常正しい答を得るのですが、そうでなかった場合、あなたはメソッドとして 関数呼び出しがコンパイルされたものを受け取ります。 これは検出が非常に困難なバグとなり得るものです。
例を挙げると、(C++ プログラマーがそうしたくなるような)new
という
メソッドの間接表記での呼び出しは、カレントのスコープですでに new
関数が
あった場合には間違ったサブルーチン呼び出しにコンパイルされてしまいます。
結果として、望んでいたクラスメソッドではなく、カレントパッケージの
new
がサブルーチンとして呼び出されることになるのです。
コンパイラはこの問題を裸の単語の require
を覚えておくことによって
避けようと試みますが、それが失敗してしまった場合にはデバッグするのが
とても面倒な結果となってしまうことになるでしょう。
この文法にも問題があります: 間接オブジェクトは名前、スカラ変数、
ブロックに限定されます; なぜなら、他の言語における
接尾辞デリファレンスと同様に、多すぎる先読みをする必要があるからです
(これらは print
や printf
のような関数におけるファイルハンドルスロットと
同様な奇妙なルールです)。
これは次に挙げる例のように、混乱した優先順位問題を
導くことになります。
move $obj->{FIELD}; # probably wrong! move $ary[$i]; # probably wrong!
これらはなんと以下のように解釈されるのです:
$obj->move->{FIELD}; # Well, lookee here $ary->move([$i]); # Didn't expect this one, eh?
あなたが期待したのはこうでしょう:
$obj->{FIELD}->move(); # You should be so lucky. $ary[$i]->move; # Yeah, sure.
間接オブジェクト構文の正しい振る舞いを得るためには、間接オブジェクトの周りに ブロックを使う必要があるかもしれません:
move {$obj->{FIELD}}; move {$ary[$i]};
この場合でも、もし現在のパッケージにたまたま move
という名前の関数が
あると、同じ潜在的問題があります。
->
記法はこれらの物騒なあいまいさのどちらの影響も受けないので、
これだけを使うことを勧めます。
しかし、結局は間接オブジェクト記法を使ったコードを読む必要が
あるかもしれないので、この記法に親しんでおくことが重要です。
UNIVERSAL
パッケージには、他の全てのクラスが自動的に継承する
以下のようなメソッドがあります。
isa
は、オブジェクトが CLASS
のサブクラスに bless されていれば
true を返します。
DOES
は、オブジェクトが ROLE
ロールを実行することを主張しているときに
真 を返します。
デフォルトでは、これは isa
と等価です。
can
はオブジェクトが METHOD
というメソッドを持っているかどうかを検査し、
持っていればそのサブルーチンに対するリファレンスを返し、持っていなければ
undef
を返します。
VERSION
はクラス(パッケージ)のバージョン番号を返します。
引数 NEED が与えられている場合、カレントバージョン(指定されたパッケージ
変数 $VERSION で定義されます)が NEED よりも小さくないことを検査します。
もし小さければ die します。
このメソッドは use
の VERSION
形式によって自動的に呼び出されます。
use Package 1.2 qw(some imported subs); # implies: Package->VERSION(1.2);
あるオブジェクトに対する最後のリファレンスが消滅したとき、そのオブジェクトは
自動的に破棄されます(これはあなたがリファレンスを大域変数に格納していて、
プログラムを終了するときでもそうです)。
もしオブジェクトが解放される直前に制御を横取りしたいのであれば、クラスの
中で DESTROY メソッドを定義することができます。
このメソッドは適切な時期に自動的に呼び出され、あなたが必要とする
クリーンアップを行うことができます。
デストラクトされるオブジェクトに対する第一引数(かつ唯一の引数)として
リファレンスを渡します。
このリファレンスは読み込み専用の値であり、デストラクタの中で
$_[0]
を操作することによって変更することはできません。
オブジェクトそれ自身(${$_[0]}
, @{$_[0]}
, %{$_[0]}
のような名前の
ついたものに対するリファレンス)は同様に強制されません。
DESTROY メソッドは予測不能な回数呼び出されるかもしれないので、
メソッドが更新する全てのグローバル変数をローカル化することが重要です。
特に、eval {}
を使うなら $@
を、system
や逆クォートを使うなら
$?
をローカル化してください。
デストラクタから抜ける前にリファレンスを再 bless するようにアレンジすると、 perl はカレントのデストラクタから呼び出した後で再 bless されたオブジェクトの ための DESTROY メソッドを再度呼び出します。 これはオブジェクトの委譲を始末するのに使ったり、あなたが呼び出すことを 選択した基底クラスのデストラクタを保証するのに使うことができます。 陽に DESTROY を呼び出すことも可能ですが、通常はそうする必要はありません。
カレントで破棄されるオブジェクトに属するオブジェクトに関して 混乱しないようにしてください。 そのようなオブジェクトはカレントオブジェクトが解放されるときに自動的に 解放・破棄が行われ、他のなにものに対するリファレンスがないようにします。
これで、ここでなすべきことは全て終わりました。 今、あなたは部屋を出てオブジェクト指向方法論に関する書籍を購入して、 そして六ヶ月かそこらの間悩む必要があるでしょう。
ほとんどの目的のために、Perl は単純かつ高速なリファレンスベースの
ガベージコレクションシステムを使用します。
このため、幾つかの段階において余計なデリファレンスが起こり、使用している Perl を
ビルドするときに -O
フラグをコンパイラに使っていなければ、性能が劣化します。
Perl をビルドするときに cc -O
を使っていれば、このデリファレンスは問題とは
ならないでしょう。
より深刻な問題は、ゼロでないリファレンスカウントを持っている アクセスできないメモリー(unreachable memory)が通常は 解放されないということです。 したがって、以下のようにすることは悪いアイデアです。
{ my $a; $a = \$a; }
$a が無くなるように思えるのですが、できないのです。 再帰的データ構造を構築したとき、メモリリークを気にしないためには自分自身で、 明示的に、自己参照を壊さなければなりません。 たとえば、木構造を扱うときに使うような自己参照構造体として以下のようなものを 考えてみます。
sub new_node { my $class = shift; my $node = {}; $node->{LEFT} = $node->{RIGHT} = $node; $node->{DATA} = [ @_ ]; return bless $node => $class; }
このようなノードを生成するとき、あなた自身が自分で自己参照を壊さない 限りノードはなくなりません(言い換えれば、これは仕様として 解釈されるものではなく、これに依存すべきではないということです)。
もう一息。
インタプリタのスレッドが最終的にシャットダウンするとき(通常は プログラムを終了するとき)、コストが掛かりますが、完全な mark-and-sweep 形式のガベージコレクションが実行されます。 そして、(シャットダウンされる)スレッドによって割り当てられたすべての ものは破棄されます。 これはPerlを組み込みに使えるようにしたり、マルチスレッドに対応できる言語と するために重要なことです。 たとえば以下のプログラムは Perl の 2 フェーズガベージコレクションを デモンストレーションします。
#!/usr/bin/perl package Subtle;
sub new { my $test; $test = \$test; warn "CREATING " . \$test; return bless \$test; }
sub DESTROY { my $self = shift; warn "DESTROYING $self"; }
package main;
warn "starting program"; { my $a = Subtle->new; my $b = Subtle->new; $$a = 0; # break selfref warn "leaving block"; }
warn "just exited block"; warn "time to die..."; exit;
/foo/test として実行したとき、以下のような出力をします。
starting program at /foo/test line 18. CREATING SCALAR(0x8e5b8) at /foo/test line 7. CREATING SCALAR(0x8e57c) at /foo/test line 7. leaving block at /foo/test line 23. DESTROYING Subtle=SCALAR(0x8e5b8) at /foo/test line 13. just exited block at /foo/test line 26. time to die... at /foo/test line 27. DESTROYING Subtle=SCALAR(0x8e57c) during global destruction.
"global destruction" がどこにあるかわかりますか? これは、スレッド ガベージコレクタがアクセスできないオブジェクトに到達したということです。
オブジェクトは常に破棄されます。
一般のリファレンス(regulaer refs)が破棄されなかった場合でもそうですし、
一般のリファレンスが分割されたパスで破棄された場合でさえ、
通常のリファレンス(ordinary refs)がオブジェクトデストラクタが自分自身を
破棄してしまったリファレンスを使うのを防ごうとする前に破棄されます。
plain なリファレンスは、そのデストラクトレベルが 0 以上であるときには
ガベージコレクションのみ行なわれます。
Perl をビルドするときに -DDEBUGGING
が有効になっていれば、
PERL_DESTRUCT_LEVEL という環境変数に対する設定をすることによって、
グローバルデストラクションのレベルを検査することができます。
さらなる情報については perlhack/PERL_DESTRUCT_LEVEL を参照してください。
より完璧なガベージコレクションの戦略は将来実装されるでしょう。
方法として最善なものは、自己再帰的なデータ構造に対するポインタを 保持するような非再帰的なコンテナクラスを作成することです。 そういったオブジェクトのクラスでの DESTORY メソッドの定義は 自己参照構造中の循環を手作業で断ち切るようなものになります。
Perl におけるオブジェクト指向プログラムに関するより親切で丁寧な チュートリアルは perltoot, perlboot, perltooc にあります。 また、その他のオブジェクトの罠や小技については perlbot を、 モジュールとクラスの作成に関するスタイルガイドについては perlmodlib を 参照してください。