クラスの定義とnewによるオブジェクトの生成,メンバ関数 |
使用ソースコードをダウンロードして参考にして下さい。
●クラス
こうした不正操作を根本的に防ぐためには,どうしたらよいでしょうか。ここで,Fig. 2.3をもう一度よく見てみて下さい。インスタンスの情報(データ)を処理すべき
正当な操作は明確にわかっています(Fig. 2.3の例では操作A,B,C)。
したがって,
・アルゴリズムの中心となるデータ
・そのデータへの正当な操作群
をまとめてひとセットとし,その他のいかなる処理からも該当データを見えないようにすれば,問題を解決することができそうです。C++には情報とそれに関連する
操作を組にするために,構造体型の概念を拡張したクラス型(class type)が用意されています。Fig. 2.6にクラス型の記述の仕方を示します。一見すると,
C言語の構造体とそっくりです。違いといえば,予約語structのかわりに新しい予約語classが使われているだけのような印象を受けます。
しかし,このクラス型にこそC言語からC++への大きな飛躍の仕組みが隠されているのです。
まず,クラス型がC言語の構造体と明らかに違うのは,メンバとして関数の定義を含めることが可能であることです。クラスのメンバとして定義された関数を
メンバ関数(member function)と呼びます(Javaで言うところのメソッド)。対して,データ部分をデータメンバ(data member)といいます。クラス型変数a
のメンバ関数f()を呼び出すときには,データメンバと同じく
a.f()
とすればよく,aへのポインタapからは
ap->f()
とすればメンバ関数f()を呼び出せます。
また,クラスの内部(メンバ宣言部)は,非公開(プライベート)部と公開(パブリック)部に分割されています。この各部分の区切りはprivateとpublicという特別
なラベルによって指定することができます。privateラベルからpublicラベルまでの間が非公開部で,publicラベルからprivateラベルまでの間が公開部です。
これらのラベルは,どのような順番で現れても構いません。また,最初のメンバからpublicラベルが現れるまでの部分は自動的に非公開部となります。なぜ,この
ように部分わけしたかというと,不正な操作によってデータメンバの値が参照されたり変更されることを防ぐためなのです。非公開部のデータメンバおよびメンバ
関数の名前には,メンバ関数の中からしかアクセスすることができないようになっているのです。一方,公開部のメンバは外部からも自由にアクセスすることが
できます。
例をList 2.6に示します。
List 2.6では,非公開データメンバのm1と,公開データメンバm2,公開メンバ関数のSetData()とPrint()を持つクラス型C1を定義
しています。メンバ関数の定義の中で,非公開メンバm2を参照している点に注目して下さい(10,14行目)。また,このリストを実行した後に30行目の//を省き
a.m1 = 0;
を有効にして,再度コンパイルしてみるとコンパイルエラーが発生するはずです。これは非公開メンバm1にアクセスしようとしてコンパイラに拒否されるためです。
このクラス型を使って実際にプログラムを記述する場合は,アルゴリズムの中心となるデータについては非公開部に,外部から呼び出されるべきメンバ関数は
公開部に置くことになります。List 2.5の単純なスタックアルゴリズムを,クラス型を使ってC++風に書き直したのがList 2.7です。
List 2.7を実行すると正しく
keep
と表示されます。次に38行目の//を外して
s1.sp = 1;
を有効にし,再度コンパイルしてみてください。コンパイルエラーとなって,List 2.5にみられたような不正な操作を未然に防いでいることがわかります。
まとめとして,Fig.2.7にクラス型がどのように不正な操作を防いでいるかを示しましょう。重要な情報は非公開部に隠されます。このような手法を情報隠蔽(information hinding)
と呼びます。そして,非公開部分をカプセルで覆うように公開部が存在し,公開部には厳選された適正な処理のみが用意されます。利用者はこの公開された
処理をインタフェースとして操作を行います。このような手法をカプセル化(encapsulation)と呼びます。
また,情報隠蔽とカプセル化によって,利用者は型が持っているデータの具体的な構造を知る必要が無くなることになります。例えば,List 2.7のスタックは基本
データ構造に配列を採用していました。他にもスタックの基本データ構造に利用できる構造としては,連結リスト(linked list)があります。これは要素をポインタ
でリスト状に繋げていくデータ構造です(Fig. 2.9)。List 2.7で,配列のかわりに連結リストでスタックを実現していても(公開されたメンバ関数の使い方が変わら
なければ),利用者は配列版であれ連結リスト版であれ,全く同様に扱えるわけです。したがって,何の配慮もせずに設計・記述された型と比べると,情報隠蔽と
カプセル化が施されたクラス型はデータの具体的な構造よりも,用意されたインタフェースによって定義される,より抽象性の高い型と考えることができるわけです。
このような理由で,データの具体的な構造によってではなく,インタフェースによって定義されると考えられる型を,抽象データ型(abstract data type)と呼びます。
実際,私たちが現実世界の事象をプログラム上に抽象化するという行為は,関連する”物(もの)”の外見上の特質と挙動,”物”が他の”物”とどのように相互作用し
合うか,という”物の持つインタフェース”の働きを再現する試みに他ならないのです。
クラス型のインスタンスのことをオブジェクト(object)と呼びます。objectは”物”という意味です。 C言語では,関数の呼び出し連鎖の間でデータが授受されて
いくという形でプログラムが動作しました。一方,C++をはじめとするオブジェクト指向言語の動作原理はどのようなものでしょう。
aObject.Print();
というメンバ関数の呼び出しを,オブジェクトaObjectへ,「プリントせよ」というメッセージ(message)を送ることだと解釈することができます。そのため,オブジェクト
指向の考え方では,メンバ関数の呼び出しのことを,「オブジェクトに対してメッセージを送信する」と表現することがあります。