多重継承
 はてさてシリーズの第1回で仮想記憶がどうとか書いたよーな気がするわけですが、 キャッシュや仮想記憶というのは、メモリ上にハコがいくつかあって、 最近使われてディスクからメモリに取って来た内容を保管しておくことで2回目以降のアクセスを高速化するものです。 で、ハコの数には上限がありまして、 ハコが全部埋まっている状態でまたディスクから新しい内容を取ってきた場合には、 ハコに入っているうち一番最後に使われた時刻が最も古いやつを追い出し(ページアウト)、 そこに新しい内容を書くわけで、 これすなわちLeast Recently Used=LRUと言われる所以です。 このページのサンプルは、ハコの数=キューの長さの上限値をつけておき、 上限値以上にEnqueueしようとすると自動的にDequeueを先に行って(ページアウト) 内容を更新する機構をシミュレートしています。
package require Itcl

class CounterArray {
    private variable hash
    constructor {} { array set hash {} }

    public method incrCount {key {value 1}} {
        if {[catch {
            incr hash($key) $value
        } e]} { set hash($key) $value }
    }

    public method getCount key {
        if {[catch {
            set r $hash($key)
        }]} { set r 0 }
        return $r
    }
}

class Moroqueue {
    protected variable Q
    constructor args { set Q $args }
    public method enqueue e {
        if {[set p [lsearch -exact $Q $e]] != -1} {
            set Q [lreplace $Q $p $p]
        }
        lappend Q $e
    }
    public method dequeue {} {
        set r [lindex $Q 0]; set Q [lreplace $Q 0 0]
        return $r
    }
    public method get {} { return $Q }
}

class PageMemory {
    inherit Moroqueue CounterArray
    private common PAGE_MAX 4

    constructor args {
        eval Moroqueue::constructor $args
        CounterArray::constructor
    } {

    }

    public method enqueue e {
        incrCount "total"
        if {[set p [lsearch -exact $Q $e]] != -1} {
            set Q [lreplace $Q $p $p]
            incrCount "cacheHit"
        } elseif {[llength $Q] >= $PAGE_MAX} {
            incrCount "pageOut"; dequeue
            lappend Q $e
        } else {
            lappend Q $e
        }
    }

    public method getResult {} {
        return [list [getCount "cacheHit"] [getCount "pageOut"]\
                     [getCount "total"]]
    }
}

set pm [PageMemory pm#auto 5]
$pm enqueue 4
$pm enqueue 6
$pm enqueue 2
$pm enqueue 3
$pm enqueue 1
$pm enqueue 4
$pm enqueue 1
$pm enqueue 2
$pm enqueue 5
$pm enqueue 2
set result [$pm getResult]
puts "ヒット[lindex $result 0]回 ページアウト[lindex $result 1]回"
# end.

 で、本題の前に前のページの続きですが、あるクラスを継承した導出クラスで、 デストラクタを定義すると、 基底クラスのデストラクタも実行され+αでこのクラスのデストラクタも実行されるわけですが、 問題はその順番です。ただ、これもこれまでさんざっぱら書いたのと同じく、 「C++やJavaと同様」です。 つまり、デストラクタは導出クラスから基底クラスに向かって順に実行されます。

 さて本題。 今度のMomoroqueueクラスはその上の2つのクラスを両方ともinheritで継承しています。 つまり、[incr Tcl]では多重継承(multiple inheritance) がサポートされています。多重継承する際には、 inherit コマンドに継承元のクラス名を並べて書きますが、 左から順に重要度の高い順に並べます。 複数のクラスから同じ名前の変数やメソッドを継承した場合、 このときの並び順で左にあるクラスほど優先されます。

    constructor args {
        eval Moroqueue::constructor $args
        CounterArray::constructor
    } {

    }
多重継承による導出クラスのコンストラクタはこのように基底クラスのコンストラクタを順番に「クラス名::」をつけて書き並べます。 この例では、1ずつ増やすことができるカウンタのハッシュ変数を管理するCounterArrayというクラスから継承した機能を使い、 キャッシュヒットの回数、ページアウトの回数、合計操作回数を管理しておき、 最後にgetResultメソッドでその結果を取り出しています。

 そういえば、Javaには多重継承がない代わりにインターフェース(interface) があるわけですが、[incr Tcl]には抽象メソッド (C++の用語では「純粋仮想関数」)とゆーもの自体がないため、 抽象メソッドの集合体であるインターフェースもありません。 というか、そもそも変数に「型」がない[incr Tcl]では、 抽象メソッドやインターフェースの意味があまりないためこれらがサポートされていないのでしょう。

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