blog20100901

2013/08/20 - プログラミング言語 Perl にまつわる etc. - Perl monger
参考 : perldoc, perldoc.jp, search.cpan.org, perldoc.perl.org ...
「 初めての Perl 第 6 版 」(オライリー・ジャパン発行 ISBN978-4-87311-567-2) 」
「 続・初めての Perl 改訂版 」(オライリー・ジャパン発行 ISBN4-87311-305-9) 」
「 Effective Perl 第 2 版 」(翔泳社発行 ISBN978-4-7981-3981-4) 」 ... etc,.

Perl Perl_6 オブジェクト指向プログラミング

Perl OOP デストラクタ (d077)

Perl OOP デストラクタ (d077)

目次 - Perl Index


Theme



Perl について、復習を兼ねて断片的な情報を掲載して行く連載その d077 回。

Perl での「 オブジェクト指向プログラミング 」( Object-Oriented Programming : OOP ) で、オブジェクトの後片付けを行う「 デストラクタ 」( destructor ) を確認する。


DESTROY



英単語「 destroy 」は、主に「 破壊する 」や「 完全に使えなくする 」といった意味を持ちます。

OOP には、オブジェクトが不要になったタイミング ( 最終的にはプログラムの終了時 ) で、そのオブジェクトを「 破壊する 」( DESTROY ) 「 デストラクタ 」( destructor ) というメソッドが用意されています。

「 デストラクタ 」メソッドは、オブジェクトを構築する「 コンストラクタ 」メソッドの反対の役割を担いますが、通常 Perl ではガベージコレクション ( GC ) の一環として暗黙的にデストラクタを呼び出すので、ユーザが意識的に定義する必要はありません。

Perl のガベージコレクションは、「 参照カウント 」( reference count (d009)) 方式を採用していますが、この方式では循環参照等を原因として「 メモリリーク 」という現象が発生する場合があります。

「 メモリリーク 」は、本来解放されるべきメモリ領域が解放されず、不要になったデータが維持され続ける現象です。

Perl OOP でも、オブジェクトのアトリビュートが循環参照になっていることがありえるので、それを発端にしてメモリリークが発生する可能性があります。

また、オブジェクトがオブジェクトの外側にあるデータリソースを使用している場合に、オブジェクト自体が消滅した後でデータリソースだけがメモリ上に取り残されることも考えられます。

「 オブジェクトの外側のデータリソース 」には、主にデータベース, ファイル, システムプロセス等があります。

例えばそれがプログラムのためのテンポラリファイルだったなら、プログラムが実行されるたびに無数のテンポラリファイルがシステム上に放置されることになります。

このような事態を避けるためには、意図的にデストラクタメソッドを定義する必要があります。

Perl のデストラクタメソッドは、コンストラクタメソッドと異なり名前として「 DESTROY 」を利用することが定められています。


# destructor
sub DESTROY {
...
}



なお、メソッド「 DESTROY 」はオブジェクトが破棄されるタイミングで実行されるので、デバッグを目的とした作業にも有用だとのことです。


DESTROY による 2 フェーズ GC のエミュレーション



perlobj から Perl の 2 フェーズガベージコレクションをエミュレートするというコードを確認します。


package Subtle;
sub new {
my $test;

# 自己参照 ( 参照カウントを増やす )
$test = \$test;
warn "CREATING ". \$test;

# $test の参照カウントは 2
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 の参照カウントは 2
# $b の参照カウントは 2

# 参照カウントを減らす
$$a = 0; # break selfref ( 自己参照を壊す )
warn "leaving block";
}
# $a の参照カウントは 0
# $b の参照カウントは 1 残る

warn "just exited block";
warn "time to die...";
exit;

# $b の参照カウントは 0

__END__
# output
starting program at yourfile01.pl line 28.
CREATING REF(0x7fe4c102a5f8) at yourfile01.pl line 15.
CREATING REF(0x7fe4c1004018) at yourfile01.pl line 15.
leaving block at yourfile01.pl line 38.
DESTROYING Subtle=SCALAR(0x7fe4c102a5f8) at yourfile01.pl line 23.
just exited block at yourfile01.pl line 43.
time to die... at yourfile01.pl line 44.
DESTROYING Subtle=SCALAR(0x7fe4c1004018) at yourfile01.pl line 23 during global destruction.



コンストラクタメソッド「 new 」では、スカラ「 $test 」を自己参照させてあります。

ですから、30 行目と 31 行目で生成したオブジェクトインスタンス「 $a 」と「 $b 」が内部にもっているアトリビュートは、その値への参照カウントがそれぞれ 2 になります。

それから、インスタンス「 $a 」に対しては、37 行目でアトリビュートの値の上書きを行い、自己参照を壊す作業をしています。これにより、「 $a 」のアトリビュートの参照カウントは 2 から 1 になります。

39 行目でブロックを抜けると、「 $a 」の参照カウントが 0 になるので、デストラクタメソッド「 DESTROY 」が起動しますが、「 $b 」の参照カウントはまだ 1 なのでデストラクタで破棄されるのは「 $a 」のみです。

最終的には 45 行目の「 exit 」でプログラムが終了しますが、ここで「 $b 」の参照カウントが 0 になるので、「 $b 」を破棄するための「 DESTROY 」が起動します。

モジュール「 Data::Dumper 」を使って、オブジェクトインスタンスの中身を確認してみます。


# in main

warn "starting program";
{
my $a = Subtle->new;
my $b = Subtle->new;

use Data::Dumper;

print "\n---\n";
print Data::Dumper->Dump([$a, $b],[qw(*a *b)]);
print "---\n\n";

$$a = 0; # break selfref ( 自己参照を壊す )
warn "leaving block";

print "\n---\n";
print Data::Dumper->Dump([$a, $b],[qw(*a *b)]);
print "---\n\n";

}

print "\n---\n";
print Data::Dumper->Dump([$a, $b],[qw(*a *b)]);
print "---\n\n";

warn "just exited block";
warn "time to die...";
exit;

__END__
# output
starting program at yourfile02.pl line 28.
CREATING REF(0x7fc26a8211f8) at yourfile02.pl line 15.
CREATING REF(0x7fc269804018) at yourfile02.pl line 15.

---
$a = bless( do{\(my $o = $a)}, 'Subtle' );
$b = bless( do{\(my $o = $b)}, 'Subtle' );
---

leaving block at yourfile02.pl line 40.

---
$a = bless( do{\(my $o = 0)}, 'Subtle' );
$b = bless( do{\(my $o = $b)}, 'Subtle' );
---

DESTROYING Subtle=SCALAR(0x7fc26a8211f8) at yourfile02.pl line 23.

---
$a = undef;
$b = undef;
---

just exited block at yourfile02.pl line 52.
time to die... at yourfile02.pl line 53.
DESTROYING Subtle=SCALAR(0x7fc269804018) at yourfile02.pl line 23 during global destruction.



ブロックを出たあとで、インスタンス「 $a 」および「 $b 」ともに値は未定義値「 undef 」(0x19) になっていますが、「 $b 」の参照カウントは 1 つ残っているので、プログラムの終了時にデストラクタ DESTROY が動いています。

出力の最後のメッセージ「 during global destruction. 」は、世界の終わりでオブジェクトの破壊が行われた、つまり、どこかで破壊し忘れたオブジェクトがあるぞ、という関数「 warn 」による警告メッセージです。

このメッセージは print 文または warn 文に改行文字を付けた場合は出力されませんので、隠れた残存オブジェクトを発見するには、print よりも warn ( 改行文字なし ) で destructing メッセージを出力した方が良さそうです。

なお、(0x24a) 等で確認した通り、本来スカラ変数の名前「 $a 」と「 $b 」は、関数「 sort 」のための特別な変数なので、通常はみだりに利用してはいけません。


入れ子のオブジェクトをデストラクト



オブジェクトにオブジェクトを入れ子状に格納 ( ネスト ) したデータ構造をデストラクトする過程を確認します。

次のコードは「 続・初めての Perl 改訂版 」( 13 章 ) を参考にしています。


# クラス 動物
package Animal;

# コンストラクタ
sub new {
my $class = shift;
my $name = shift || 'Tarou';
bless { Name => $name }, $class;
}

# 名前へのアクセサ
sub name {
my $either = shift;
ref $either ? $either->{Name} : "an unnamed $either";
}

# Animal のデストラクタ
sub DESTROY {
my $self = shift;
warn '[', $self->name, " has died.]\n";
}

# クラス 牛
package Cow;
our @ISA = qw(Animal);

# クラス 家畜小屋
package Barn;

# コンストラクタ
sub new { bless [], shift }

# セッター
sub add {
my $self = shift;
push @{$self}, shift;
}

# ゲッター
sub contents {
my $self = shift;
@{$self};
}

# Barn のデストラクタ
sub DESTROY {
my $self = shift;
print "$self is being destroyed...\n";
foreach ($self->contents) {
print ' ', $_->name, " goes homeless.\n";
}
}



上記コードには、まず、クラス「 Animal 」とこれを継承したクラス「 Cow 」があります。

クラス「 Animal 」には、コンストラクタ「 new 」と動物の名前のためのアクセサ「 name 」、それからデストラクタ「 DESTROY 」があります。

クラス「 Cow 」は、今のところ「 Animal 」を継承しているだけです。

この他にクラス「 Barn 」( 家畜小屋 ) があります。

クラス「 Barn 」のコンストラクタ「 new 」は、アトリビュートとして空の配列のリファレンスをもったインスタンスを作成します。

セッター「 add 」は、そのインスタンスをデリファレンスした配列に、要素としてメソッドの引数をセットします。

ゲッター「 contents 」は、デリファレンスした配列を返します。

クラス「 Barn 」にもデストラクタ「 DESTROY 」が用意されていて、この中で破棄されるインスタンスが持っているデータを通知します。

このプログラムは、main プログラムで次のように利用します。


# in main

# コンストラクト
my $barn = Barn->new;

# クラス Cow のインスタンスを 2 つ追加
$barn->add(Cow->new('cow_01'));
$barn->add(Cow->new('cow_02'));



このとき、オブジェクトインスタンス「 $barn 」の中身は次のようになっています。


use Data::Dumper;
print Data::Dumper->Dump([$barn],[qw(*barn)]);

__END__
# output
@barn = bless( (
bless( {
'Name' => 'cow_01'
}, 'Cow' ),
bless( {
'Name' => 'cow_02'
}, 'Cow' )
), 'Barn' );



Barn のオブジェクトの中に 2 つの Cow のオブジェクトが入れ子状に格納されていることがわかります。


家畜小屋を壊す



家畜小屋の役割をもったクラス「 Barn 」のインスタンス「 $barn 」には、現在 2 頭の牛がいます。


foreach ($barn->contents){
print $_->name, "\n";
}

__END__
# output
cow_01
cow_02



この状態で、インスタンス $barn のデータを壊します。データの破壊には未定義値をセットする演算子「 undef 」(0x19) を使い、その後プログラムは終了します。


# 家畜小屋を燃やす
print "Burn the barn:\n";
$barn = undef;

print "End of program.\n";



このプログラムを実行すると次の出力が得られます。


Burn the barn:
Barn=ARRAY(0x7f872b829ec0) is being destroyed...
cow_01 goes homeless.
cow_02 goes homeless.
[cow_02 has died.]
[cow_01 has died.]
End of program.



この内次のメッセージは、クラス「 Barn 」のデストラクタが出力したものです。


Barn=ARRAY(0x7f872b829ec0) is being destroyed...
cow_01 goes homeless.
cow_02 goes homeless.



デストラクタは、Barn のインスタンス「 $barn 」の値が undef になり、オブジェクトのデータへの参照カウントがなくなったことで発動します。

その後すぐに次のメッセージが出力されています。


[cow_02 has died.]
[cow_01 has died.]



これは、クラス Barn のインスタンス $barn の道連れで参照カウントがゼロになるネストされたオブジェクトのクラス Cow ( の親クラス Animal ) のデストラクタが出力したメッセージです。


参照カウントを増やす



家畜小屋 ( barn ) にいれる牛の参照カウントを増やしてみます。

参照カウントを増やす方法として、今回は配列変数「 @cows 」にクラス「 Cow 」のオブジェクトインスタンスを格納してから、セッターメソッド「 add 」に渡す方法を使います。


my $barn = Barn->new;
my @cows = (Cow->new('cow_01'), Cow->new('cow_02'));
$barn->add($_) for @cows;



これにより、クラス Cow のインスタンスが配列 @cows とクラス Barn のインスタンス $barn から参照されることになります。

この時、配列 @cows は次のようにデータを格納しています。


print "$_\n" for @cows;

__END__
# output
Cow=HASH(0x7fae98028d20)
Cow=HASH(0x7fae9803f450)



この状態でクラス Barn のデストラクタを発動します。


# 家畜小屋を燃やす
print "Burn the barn:\n";
$barn = undef;



これにより次の出力が得られます。


Burn the barn:
Barn=ARRAY(0x7fae980286c0) is being destroyed...
cow_01 goes homeless.
cow_02 goes homeless.



しかし、前項のケースと異なりクラス Cow のデストラクタは発動しません。なぜなら、まだこの時点では、クラス Cow のインスタンスに対する参照カウントを配列 @cows が持っているからです。

クラス Cow のデストラクタを発動するには、次のようにして @cows が持っているインスタンスへの参照カウントをゼロにします。


print "Lose the cows:\n";

# 配列を空にする
@cows = ();

print "End of program.\n";



これによってクラス Cow のデストラクタが発動し、次の出力が得られます。


Lose the cows:
[cow_02 has died.] at yourfile06.pl line 24.
[cow_01 has died.] at yourfile06.pl line 24.
End of program.




NEXT



今回は、デストラクタ「 DESTROY 」が起動するタイミングを確認しました。

次回は、デストラクタの実際的な利用方法を確認します。


参考情報は書籍「 続・初めての Perl 改訂版 」, 「 Effective Perl 第 2 版 」を中心に perldoc, Wikipedia および各 Web サイト。それと詳しい先輩。

目次 - Perl Index


































同じカテゴリー(Perl)の記事
 Perl mp2 翻訳 Web コンテンツ圧縮の FAQ (d228) (2023-10-11 23:49)
 Perl mp2 翻訳 既知のブラウザのバグの回避策をいくつか (d227) (2023-05-26 15:41)
 Perl mp2 翻訳 Perl と Apache でのキュートなトリック (d226) (2023-05-19 17:05)
 Perl mp2 翻訳 テンプレートシステムの選択 (d225) (2022-08-15 22:23)
 Perl mp2 翻訳 大規模 E コマースサイトの構築 (d224) (2022-06-15 20:43)
 Perl mp2 翻訳 チュートリアル (d223) (2022-06-15 20:42)
上の画像に書かれている文字を入力して下さい
 
<ご注意>
書き込まれた内容は公開され、ブログの持ち主だけが削除できます。

Llama
リャマ
TI-DA
てぃーだブログ
プロフィール
セラ (perlackline)
セラ (perlackline)
QRコード
QRCODE
オーナーへメッセージ

PAGE TOP ▲