Version 17.18
「インターフェイス」という考え方
「前回はソートに抽象クラスと純粋仮想関数を使用してみました」
『ソートの比較をする所で使ってたんだよね』
「そう。使う側からの視点で整理すると、まず int 型の配列があって、そ
れをソートする関数がすでに用意されています」
// ソートを行う関数です。
void DoBubbleSortUseComapre
( int *p_piAry, int p_iSize, CComparetor *p_pcComparetor )
{
// (略)
}
「この関数に配列を渡すことでソートされますが、その際、昇順か降順かを
第3引数で決めることができます」
『 CIntComparetor クラスなら昇順で、 CIntDescComparetor クラスなら
降順ね』
「というわけで、 CIntComparetor クラスの変数を渡して、昇順でソート
しているのが以下の箇所」
// 比較用クラスの変数を用意します(昇順)。
CIntComparetor cIntComparetor;
// ソートします。
DoBubbleSortUseComapre( iAry, 5, &cIntComparetor );
「こうすることで、第3引数に CIntComparetor クラスの変数のポインタが
渡されます。このポインタを通して、さっきの DoBubbleSortUseComapre()
関数内で〈要素の比較〉を行う時に CompareTo() メンバ関数を呼び出しま
す」
『このメンバ関数を呼び出すところで仮想関数の仕組みが使われてるわけね』
「そういうこと。ここで〈左が大きい時に true を返す〉ってしてるから
昇順でソートされるわけです」
『うん、ひとつひとつ追っていくと分かるんだけど……』
「じゃあ、今度は〈作る側〉の視点で見てみようか」
『作る側?』
「そう、これからソートをする関数を作るとします」
『うん』
「普通に作る場合、 Version 13.09 ( No.245 ) の DoBubbleSort() 関数の
ように作ることになります」
void DoBubbleSort( int *p_piAry, int p_iSize )
{
// 「入れ替え先」のループです。
for( int iOut = 0; iOut < p_iSize - 1; iOut++ )
{
// 最後から先頭方向へのループです。
// ただし「入れ替え先」までです。
for( int iIn = p_iSize - 1; iOut < iIn; iIn-- )
{
if( p_piAry[iIn - 1] > p_piAry[iIn] ) // ←ここ。
{
// 前の方が大きいので入れ替えます。
int iTemp = p_piAry[iIn];
p_piAry[iIn] = p_piAry[iIn - 1];
p_piAry[iIn - 1] = iTemp;
}
}
}
}
「だけど、この中で〈比較を行う箇所〉、つまりこの DoBubbleSort()
関数の〈ここ〉は、できれば汎用化したいと考えます」
『なんで?』
「 Version 13.10 ( No.246 ) とかで言ってきたけど、データの種類、つま
配列の要素の型や、ソートの方法、つまり昇順や降順、構造体のどのメンバ
で比較するかによって、この比較処理を変える必要があるから」
『そういえば、それ以外のとこって変えなくていいんだよね』
「そう、ソートのアルゴリズムそのものはデータの種類に関係ないからね」
『で、ここを関数ポインタにしていたのが、 Cランタイムライブラリの
qsort() 関数なんだよね』
「 Version 13.17 ( No.253 ) で説明したね。 qsort() 関数は
クイックソートをする関数だけど、この関数でどんな要素の配列でもソート
できるようにするためには」
『比較するところを渡す要素ごとに変えなきゃいけない!』
「なので、関数を作って、その関数ポインタを渡すことで、 qsort() 関数
の中から呼び出してもらうようにするわけです」
『……あ!』
「なに?」
『これにポリモーフィズムが使える、っていう話なんだ!』
「そういうこと。この関数ポインタの代わりに、ポリモーフィズムを使って
実現できるわけです。 DoBubbleSort() 関数の〈ここ〉をポリモーフィズム
で処理するために、まず Version 13.09 ( No.245 ) と同じく関数にします」
void DoBubbleSortUseComapre( int *p_piAry, int p_iSize )
{
// 「入れ替え先」のループです。
for( int iOut = 0; iOut < p_iSize - 1; iOut++ )
{
// 最後から先頭方向へのループです。
// ただし「入れ替え先」までです。
for( int iIn = p_iSize - 1; iOut < iIn; iIn-- )
{
if( CompareTo( p_piAry[iIn - 1], p_piAry[iIn] ) // ←ここ
== true )
{
// 前の方が大きいので入れ替えます。
int iTemp = p_piAry[iIn];
p_piAry[iIn] = p_piAry[iIn - 1];
p_piAry[iIn - 1] = iTemp;
}
}
}
}
「次に、これを適当なクラスのメンバ関数にします。ここではクラスを
CComparetor クラスにしておきます」
// ソートを行う関数です。
void DoBubbleSortUseComapre
( int *p_piAry, int p_iSize, CComparetor *p_pcComparetor )
{
// 「入れ替え先」のループです。
for( int iOut = 0; iOut < p_iSize - 1; iOut++ )
{
// 最後から先頭方向へのループです。
// ただし「入れ替え先」までです。
for( int iIn = p_iSize - 1; iOut < iIn; iIn-- )
{
if( p_pcComparetor->CompareTo // ←ここ
( p_piAry[iIn - 1], p_piAry[iIn] ) == true )
{
// 前の方が大きいので入れ替えます。
int iTemp = p_piAry[iIn];
p_piAry[iIn] = p_piAry[iIn - 1];
p_piAry[iIn - 1] = iTemp;
}
}
}
}
「ただの関数を、 CComparetor クラスのメンバ関数にしているだけだから
ね。引数と戻り値は同じだから。ただ、このクラスの変数をポインタにする
のを忘れないようにね」
『ポインタにしないとポリモーフィズムにならないもんね』
「次に CComparetor クラスを作ります。 CompareTo() メンバ関数を持たせ
て、これを純粋仮想関数にします」
// 比較用クラスの基本クラス。
class CComparetor
{
public:
// 比較用メンバ関数。純粋仮想関数です。
virtual bool CompareTo( int p_iL, int p_iR ) = 0;
};
『純粋仮想関数にしなきゃだめ? ただの仮想関数でもいいんじゃない?』
「ただの仮想関数にした場合、以下の問題が出てきます」
・オーバーライドしなくてもいい
→オーバーライドし忘れることがある
→メンバ関数名や引数等を間違えてオーバーライドできてないことがある
・デフォルトの処理が必要
→ソート対象の「デフォルトの型」が決まってないから無理
「まず、純粋仮想関数にしておけば、オーバーライドできていないと
コンパイルエラーになります」
『え、そうなの?
「 Version 17.16 ( No.371 ) で説明したように、純粋仮想関数は〈中身の
ないメンバ関数〉で、変数が作れないから』
『そか、オーバーライドできてないと中身がないままだから、変数が作れな
いんだ』
「だから純粋仮想関数の方が安全、っていうのがあります。あと純粋仮想関数
しておかないと中身を作る必要があるから」
『比較処理を書かなきゃいけないけど、それが無理ってことね』
「だから、これは純粋仮想関数がいいかな」
『で、純粋仮想関数を作ったら、あとは CIntComparetor クラスとかを作っ
て CompareTo() メンバ関数をオーバーライドすればいいわけね』
「このようにすることで、関数ポインタの代わり、つまり〈引数でポインタ
を渡すことで、関数内で呼び出してもらう〉ことができるわけです」
『コールバックしてもらえるわけねー』
「これがポリモーフィズムの使用目的のひとつかな」
『つまり、関数ポインタみたいに、関数の中から呼び出してもらうことがで
きる、そのために使えばいいってわけね』
「で、この CComparetor クラスみたいなクラスを【インターフェイス】と
言います」
『いんたーふぇいす?』
「ユーザーインターフェイスとかと同じ〈接点〉って意味だけど、それより
も〈操作するためのコネクタ〉って考えると分かりやすいかな。たとえば
DoBubbleSortUseComapre() 関数をもう一度見てみようか」
// ソートを行う関数です。
void DoBubbleSortUseComapre
( int *p_piAry, int p_iSize, CComparetor *p_pcComparetor )
「この関数がすでに提供されている場合、使う側は、CComparetor クラスの
派生クラスを作り、 CompareTo() メンバ関数をオーバーライドして、第3引数
にポインタを渡せばいいわけです」
『うん、そうなるね』
「ということはつまり、 DoBubbleSortUseComapre() 関数の〈比較処理〉を
呼び出す箇所がここから外に出ていて、そこに CComparetor クラスの
派生クラスを〈接続〉することで使ってもらえる、ってこと」
『?』
「図にするとこうなるかな」
┌DoBubbleSortUseComapre┐
│ ・比較処理の呼び出し │
└────↓──────┘
↓
(第3引数・ CComparetor クラスのポインタ)
↓
↓
CompareTo() メンバ関数の呼び出し
↑ ↑
こっちを渡すと↑ こっちを渡すと↑
昇順ソート ↑ 降順ソート ↑
┌─────┴─────┐ ┌──────┴──────┐
│CIntComparetor クラス │ │CIntDescComparetor クラス │
└───────────┘ └─────────────┘
「つまり、第3引数が〈比較処理の接続口〉になっていて、ここに
CIntComparetor クラスの変数をくっつけると昇順ソート、
CIntDescComparetor クラスの変数をくっつけると降順ソートになる、そう
いう機能を持っているから、 CComparetor クラスのようなクラスを
【インターフェイス】って言うんです」
/*
Preview Next Story!
*/
『……うううっ、ちょっと分からないかもっ』
「うん、この辺がポリモーフィズムで一番難しいところかもね」
『次回もこの続きなんだねっ』
「うんもちろん。……それで良かった?」
『いいような悪いような……』
「というわけで次回」
< Version 17.19 インターフェイスの使い方 >
『につづく!』
「とりあえず、ちゃんと理解するまでは説明するから」
『うん、その方がいいかも』
「まぁ嫌って言っても説明するけど」
『オニー!』