平田智剛のブログ

立法、行政、司法、報道、そして科学

F.ism.does_harm_to(!F);

多重継承を許す場合の循環継承の見つけ方

(同じ記事をqiitaにも上げています)

qiita.com

0.目的

クラスの多重継承を許す場合、継承の循環の起こり方が複雑になる。
そのような場合に循環継承を効率的に見つけるアルゴリズムを考えてみる。

1.循環継承とは

そもそも循環継承とは何か。
視覚的には図1.1のように、「継承の単方向輪」ができることである。

image.png

図1.1 単一継承の場合の循環継承の例
 
これを言葉で説明すると、「自分が自分自身の先祖や子孫となる」関係である。図1.1の例では、AはA自身の曾祖父母あるいは曽孫であるといえる。

多重継承の場合、直系が複数通りになるため、「どの直系で循環継承が起きているか」を考える必要が出てきてしまう。

2.考案するアルゴリズム

すべての「子クラスを持たないクラスc0」に対して次の方法を適用することで、系内の循環継承を見つけることができる。但し、cic0直系尊属i親等な任意のクラスである。

  1. i=0とする。
  2. ciの子孫集合D_1を考える
  3. ciの親の配列Ci+1を考える。
  4. Ci+1の各元ci+1の子孫集合D_2を考える。
  5. ci+1の循環参照がないことの真偽はD_1 \cap D_2 = \phiと同値である
  6. iをインクリメントする。

なお、このアルゴリズムには次の2つの定理を採用している。

  • すべての「子を持たないクラス」の先祖を辿れば、系内全てのクラスにアクセス可能である (2-1節で詳解)
  • 子孫にdを持つクラスcの任意の親pが子孫にdを持つことと、pが循環継承していることは同値である (2-2節で詳解)

2-1.系内全てのクラスに最低1度は確実にアクセスする方法

すべての「子を持たないクラス」の先祖を辿れば、系内全てのクラスにアクセス可能である

全てのクラスは「子を持たないクラス」と「子を持つクラス」に分けられる。
「子を持つクラス」は何かしらのクラスから見た親クラスである。
いま、クラスについて①②に場合分けして考えよう。
「子を持つクラス」の子が「子を持たないクラス」である場合、子の先祖を辿ることでアクセス可能だ。・・・①
「子を持つクラス」の子が「子を持つクラス」である場合、子についてもう一度場合分けして考える。・・・②
②に分類される限り永遠に場合分けが続き、停止するのは①に分類される場合のみ。
循環継承がない限り、卑属の世代数は有限であるため、すべてのクラスは必ずいつか①に分類される。
循環継承がある場合、循環が起きているということは、すでに循環継承に関わるクラスにはすべてアクセス済みであるということである。

2-2.循環参照がそこにあるかの判定法

各クラスの親の1次元配列を考えよう。
このとき、各クラスの親もクラスであるため、各々の親の1次元配列を持っている。
そのため、各クラスは祖父母の2次元配列を持っているといえる。
同様に考えることで、一般に各クラスは直系尊属n親等のn次元配列を持っているということができる。
ここであるクラスをcと呼ぶこととする。cの先祖の任意の多次元配列にcが含まれることは、
cが循環継承しているということと同じである。
つまり命題「cの先祖配列 \ni c ⇔ cは循環継承している」は真である。
この命題は次のように書き換えられる。
cの先祖配列 \ni c' かつc'=c ⇔ c'は循環継承している」
ここで「cの先祖配列 \ni c' 」とは、cc'の子孫といえることと同じである。
c'からみてcの先祖配列とは、自身を含む先祖配列であり、その配列の少なくとも1つの元について、子孫にcを持つものである。
したがって、任意の「cを含む先祖配列」において、その元の少なくとも1つが子孫にc'(=c)を持つことと、c'が循環継承していることは同値だ。
ここで、「子孫にdを持つ任意のクラスc」の親クラスの先祖配列P_1(c)を考えよう。
cの任意の親をp_1(c)と呼ぶとき、P_1(c)は「p_1(c)を含む先祖配列」ということができる。
P_1(c)の元の少なくとも1つが子孫にp_1(c)を持つことと、p_1(c)が循環継承していることは同値である。
但し、子孫にp_1(c)を持つとき、必ず子孫に「p_1(c)の子孫」をも併せ持つ。つまり子孫として必ずcdを持つというわけだ。
したがって、次のように言うことができる。

「子孫にdを持つクラスcの任意の親pが子孫にdを持つことと、pが循環継承していることは同値である」by 17ec084