抽象クラスと仮想関数を使った実例


抽象クラスと仮想関数をつかったシナリオ

これまで学んだ抽象クラスや仮想関数が実際にどのように使われるかを、以下のようなシナリオとともに、ほんのちょっとだけ大きなプログラムで考えてみましょう。AliceとBobのコンビに登場してもらいます。

Alice は、要素の追加、要素の並びの反転、要素に対して印刷の指示、といった機能を持つコンテナ(なにかを格納するもの)をコーディングします。格納されるものの実態はよく知らされていませんが、Print() という印刷を行う仮想関数を持っていることは知っています。

Bob は、Aliceのコンテナを使って整数や文字列を格納し、これらの印刷と並びを逆順にする機能を使いたいとします。

Aliceは、並び替えを行う要素の基本クラスとなる Elem抽象クラスと、これを管理するListクラスを書いてBobに提供します。ListクラスはElemクラスのポインタの管理をします。実際にどのようなオブジェクトへのポインタが格納されるかは知りませんが、Elemクラスを使って並べ替えのコードを書くことができます。

Bob は Aliceに提供された Elemクラスを派生して、自分が実際に使いたい IntegerクラスとStringクラスを実装します。インスタンス化したIntegerとStringのポインタを、Aliceが提供してくれた Listクラスで管理しますが、実際にどのようなアルゴリズムで並べ替えをしているかは知りません。継承と仮想関数を使った多態の性質により、IntegerやStringといった性質の異なるオブジェクトも区別なくひとつのコンテナに格納することができます。

 

それでは、コードは以下のようになります。

list.h

list.cpp

main.cpp

練習問題8-1

上記のプログラムを実行して動作を確認しなさい。プロジェクト名は p3k3_8 とすること。

このプログラムでは、Listクラスのデストラクタ中においてリストに格納されている要素の delete を行っている。しかし、Stringクラスのデストラクタが正常に呼び出されていない。(そのため、文字列のために確保した領域がメモリリークしている。)

プリント文を入れたり、デバッガを使ったりしてデストラクタの呼び出し状態を確認しなさい。また、それはなぜかを考察し、Stringクラスのデストラクタが正しく呼び出されるように修正し、メモリリークがなくなることを確認しなさい。

(ヒント:デストラクタの仮想化)

練習問題 8-2

以下の課題は、練習問題 8-1で修正した以外は、list.hおよびlist.cppには一切手を加えずに実現すること。

2次元座標を管理する Coordクラスを Integerクラスの派生クラスとして定義し、動作を確認しなさい。このとき、Integerクラスに修正が必要であれば施してもよい。Printの書式は自分で工夫して作成すること。

(補足: Coordクラスを作成するときには、すでに Integerクラスが持っている変数を利用してください。)

練習問題 8-3

コンテナに、要素の削除の機能を追加して使用したい。Listクラスを派生して 要素の削除を行う Delete メンバ関数を持った List2クラスを作成し、動作を確認しなさい。Deleteメンバ関数の仕様は、以下のようにすること。また、ファイルは適切に分割するようにしなさい。

void List2::Delete(int index);

int index: 削除する要素の添え字 (0-based ゼロから始まる添え字)

動作例

List2 list;

list.Add(new Integer(1));
list.Add(new Integer(2));
list.Add(new Integer(3));

list.Print();
cout << "---" << endl;

list.Delete(1); // 1番目の要素を削除    
list.Print();
  
実行結果

1
2
3
---
1
3