これまでの章では、C++の基本的な文法やメモリの扱い方について学んできました。この章からは、C++の最も強力な機能の一つであるオブジェクト指向プログラミング (Object-Oriented Programming, OOP) の世界に足を踏み入れます。OOPの考え方を身につけることで、より大規模で複雑なプログラムを、現実世界の「モノ」の概念に近い形で、直感的に設計・実装できるようになります。その第一歩として、OOPの中核をなすクラスの基礎を学びましょう。
他のプログラミング言語でオブジェクト指向に触れたことがあるなら、「クラスはオブジェクトの設計図」という説明を聞いたことがあるかもしれません。C++でもその考え方は同じです。クラスは、ある「モノ」が持つべき**データ(属性)と、そのデータに対する処理(操作)**を一つにまとめたものです。
このように、関連するデータと処理を一つのクラスにまとめることを、OOPの重要な概念の一つであるカプセル化 (encapsulation) と呼びます。💊
例として、「人」を表すPersonクラスを考えてみましょう。「人」は「名前」や「年齢」といったデータ(メンバ変数)を持ち、「自己紹介する」といった処理(メンバ関数)を行うことができます。
class Person {
public:
// メンバ変数
std::string name;
int age;
// メンバ関数
void introduce() {
std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl;
}
};class Person { ... }; という構文でクラスを定義します。クラス定義の最後にはセミコロン;が必要なので忘れないようにしましょう。現時点では、public:というキーワードは「これらのメンバは外部からアクセスできます」という意味だと考えておいてください。詳細は後ほど説明します。
クラスはあくまで「設計図」です。実際にプログラムで利用するためには、この設計図をもとに実体を作る必要があります。クラスから作られた実体のことをオブジェクト (object) またはインスタンス (instance) と呼び、オブジェクトを作ることをインスタンス化 (instantiation) と言います。
インスタンス化の構文は、変数の宣言とよく似ています。
このように、クラス名 インスタンス名; という形でインスタンスを生成できます。インスタンスのメンバ変数やメンバ関数にアクセスするには、インスタンス名.メンバ名 のようにドット演算子 (.) を使います。taroとhanakoは同じPersonクラスから作られたインスタンスですが、それぞれが独立したデータを持っていることがわかります。
先ほどのPersonクラスの例では、main関数からtaro.age = 30;のようにメンバ変数に直接アクセスできました。これは手軽ですが、問題を引き起こす可能性があります。例えば、年齢にマイナスの値や非現実的な値を設定できてしまうかもしれません。
Person jiro;
jiro.name = "Jiro";
jiro.age = -5; // 本来ありえない値が設定できてしまう!
jiro.introduce();このような意図しない操作を防ぐために、C++にはアクセス制御の仕組みがあります。クラスのメンバは、外部からのアクセスの可否を指定できます。
public: クラスの外部(main関数など)から自由にアクセスできます。private: そのクラスのメンバ関数からしかアクセスできません。外部からはアクセス不可です。アクセス制御の基本は、メンバ変数はprivateにし、メンバ関数はpublicにすることです。これにより、データの不正な書き換えを防ぎ、クラスの内部実装を外部から隠蔽します。これを情報の隠蔽 (information hiding) と呼び、カプセル化の重要な目的の一つです。
privateなメンバ変数に安全にアクセスするために、publicなメンバ関数(ゲッターやセッターと呼ばれる)を用意するのが一般的です。
setAge関数内で値の妥当性チェックを行っている点に注目してください。このように、クラスの利用者は内部の実装を気にすることなく、提供されたpublicなインターフェース(メンバ関数)を通じて安全にオブジェクトを操作できます。
constキーワード:getName() constのようにメンバ関数の後ろにconstを付けると、その関数がメンバ変数を変更しないことをコンパイラに約束します。このような関数をconstメンバ関数と呼びます。
オブジェクトは生成され、利用され、やがて破棄されます。このライフサイクルに合わせて特別な処理を自動的に実行するための仕組みがコンストラクタとデストラクタです。
コンストラクタは、インスタンスが生成されるときに自動的に呼び出される特別なメンバ関数です。主な役割は、メンバ変数の初期化です。
コンストラクタには以下の特徴があります。
voidも付けない)。このように、インスタンス生成時に()で初期値を渡すことで、オブジェクトを生成と同時に有効な状態にできます。set関数を別途呼び出す手間が省け、初期化忘れを防ぐことができます。
デストラクタは、インスタンスが破棄されるとき(例えば、変数のスコープを抜けるとき)に自動的に呼び出される特別なメンバ関数です。主な役割は、オブジェクトが使用していたリソース(メモリやファイルなど)の後片付けです。
デストラクタには以下の特徴があります。
~ + クラス名。実行結果を見ると、kenjiオブジェクトが生成されたときにコンストラクタが、create_person_scope関数のスコープを抜けるときにデストラクタが自動的に呼び出されていることがわかります。動的に確保したメモリの解放など、クリーンアップ処理はデストラクタに書くのが定石です。この考え方は、今後の章で学ぶRAII(Resource Acquisition Is Initialization)という重要な概念に繋がります。
この章では、C++におけるオブジェクト指向プログラミングの第一歩として、クラスの基本的な概念を学びました。
public, private)により、外部からアクセスされたくないメンバを保護します(情報の隠蔽)。クラスを使いこなすことで、プログラムの部品化が進み、再利用性やメンテナンス性が格段に向上します。次の章では、クラスのさらに進んだ機能について学んでいきましょう。
幅(width)と高さ(height)をメンバ変数として持つRectangleクラスを作成してください。
privateで定義してください。getArea()メソッドと、周の長さを計算して返すgetPerimeter()メソッドをpublicで実装してください。main関数でRectangleクラスのインスタンスをいくつか生成し、面積と周の長さを表示するプログラムを作成してください。タイトル(title)、著者(author)、ページ数(pages)をメンバ変数として持つBookクラスを作成してください。
privateで定義してください。printInfo()メソッドをpublicで実装してください。(例: Title: [タイトル], Author: [著者], Pages: [ページ数] pages)main関数でBookクラスのインスタンスを生成し、その情報を表示してください。