デストラクタ
 今度は、enqueue・dequeueの度にキューの状態をログファイルに保存できるようにしてみました。
package require Itcl
class Moroqueue {
    private variable Q
    private variable LogName

    constructor {filename args} {
        set LogName $filename
        set fout [open $LogName w]
        puts $fout "*** constructor of object: $this ***"
        close $fout
        set Q $args
    }

    destructor {
        set fout [open $LogName a]
        puts $fout "[clock format [clock seconds] -format {%Y/%m/%d %H:%M}]"
        puts $fout "*** destructor of object: $this ***"
        close $fout
    }

    private method isfilled {} {
        if {[llength $Q] >= 50} { return 1 } { return 0 }
    }

    private method writeLog {} {
        set fout [open $LogName a]
        puts $fout [$this get]
        close $fout
    }

    public method enqueue e {
        if {[isfilled]} {
            error "Ootto! Queue size exceeds maximum."
        }
        if {[set p [lsearch -exact $Q $e]] != -1} {
            set Q [lreplace $Q $p $p]
        }
        lappend Q $e
        $this writeLog
    }

    public method dequeue {} {
        set r [lindex $Q 0]; set Q [lreplace $Q 0 0]
        $this writeLog
        return $r
    }

    public method get {} { return $Q }
}

set m1 [Moroqueue mo#auto log.txt 5 6 7]
$m1 enqueue 4;
$m1 dequeue
$m1 enqueue 8;
$m1 enqueue 7;
$m1 enqueue 8;
$m1 dequeue
delete object $m1
# end.

 インスタンス(オブジェクト)がもういらなくなったら、 delete objectコマンドで削除することができます。 で、このコマンドが使われてインスタンスが消える直前に何か処理をさせたい場合、 これを書いておく特殊なブロックがデストラクタ と呼ばれるもので、 [incr Tcl]ではdestructorコマンドの引数にその処理を記述します。
    destructor {
        set fout [open $LogName a]
        puts $fout "[clock format [clock seconds] -format {%Y/%m/%d %H:%M}]"
        puts $fout "*** destructor of object: $this ***"
        close $fout
    }
デストラクタは(C関数のと同じ意味の)引数を取ることはできません。 これはC++のデストラクタやJavaのファイナライザと共通しているので、 すぐ理解できると思います。

 Javaの考えに慣れている場合は注意してください。[incr Tcl]では、 最後までdelete object を使わなければ、各インスタンスの削除処理はされないままプログラムが終了します (デストラクタは実行されません)。

 ところで、これはかなり頻繁にはまる落とし穴ですが、 メソッドの名前に「open」や「set」のような、標準Tcl言語のコマンドと同じ名前を使うこともできますが、 この場合、このクラスのメソッドの中で単に「open」とすると、 どちらが優先されるかというと、クラスのメソッドが優先されます。 …ということは、このクラスの中では、 Tcl言語のopenコマンドはそのままでは使えなくなってしまいます。 そういうときには、「::open」や「::set」のように明示的に「::」 を頭につけるとTcl言語のコマンドとして解釈されます。 Tcl/Tkの名前空間は [incr Tcl] のそれが元になったことは先述しましたが、 その名前空間を考えるとわかりやすいでしょう。

 逆に、[incr Tcl]でも、 C++やJavaでおなじみのthisキーワードを使うこともできます。 例えば、あるクラスのメソッド内で、 後々になってevalさせるコマンドを文字列として作っておくなどの場合に、 自分自身のインスタンス名(オブジェクトハンドル)として $this を使うことができます。 また、メソッドをTclコマンドや通常のTclプロシージャと明確に区別するために、

  $this writeLog
このように自分のクラスのメソッド呼び出しに明示的に $this をつけても構いません。 よくあるのが、Tkのウィンドウをひとつのクラスとして定義したとき、 ボタンの-command、bindコマンドでバインドする処理、 afterコマンドで登録する処理にクラスのメソッドを使うときです。 これらはすべてグローバルレベル(どのクラスやプロシージャにも属さないところ) で実行されるので、インスタンス名 $this を明示的に使う必要があります。
class HelloWindow {
    constructor {title} {
        toplevel .g
        wm title .g $title
        label .g.la -text Hello
        button .g.cmda -text Push  -command "$this hello"
        button .g.cmde -text Close -command "$this hide .g"
        #  -commandはグローバルレベルで解釈されるので、
        #  必ず $this が必要です
        pack .g.cmda .g.cmde -side left
    }

    destructor { catch {destroy .g} }
    method hello {} { tk_messageBox -message Hello! }
    method hide {w} { wm withdraw $w }
}

拡張レビュー分室 top
(first uploaded 1999/10/22 last updated 2000/11/18, EK)