【C++】カプセル化とは?わかりやすく解説!

この記事で下のようなことが書かれています。

  • カプセル化ってどういう意味?
  • privateとpublicって?
  • カプセル化のメリットは?

このような疑問をプログラミングが初めての方にもわかるように、わかりやすく解説しますオブジェクト指向の3つの特徴の1つのカプセル化というのはどういうものなのでしょうか。見ていきましょう!

カプセル化(encapsulation)とは?

カプセル化とは、オブジェクトの情報を隠蔽することによって、不整合を引き起こすような操作をできなくさせる仕組みです。最初は「ふーん。」程度の感覚で見てもらって大丈夫です。下に行くにつれて詳しく解説して行きます。

カプセル化の仕組み

簡単にC++のサンプルコードでカプセル化の仕組みを見ていきましょう。

class A {
	int x;
public: //アクセス指定子
	int y;
};

int main() {
	//クラス「A」のオブジェクト「a」を作成
	A a;

	a.x = 1; //×アクセス不可
	a.y = 1; //○アクセス可能
}

ここで、クラスAのxはカプセル化されていて、yはカプセル化されていません。その違いは「public:」以前に定義されているかされていないかの違いです。

カプセル化されているメンバ変数やメンバ関数にはクラスの外からはアクセスできません

なのでmain文からは、aのxにアクセスしようとしてもできないことがわかります。反対にaのyはpublic:によってカプセル化が解かれているのでmain文からでもアクセスができるようになっています。

カプセル化で重要なprivateとpublic

クラス内でメンバを定義する際に、int(整数型)やchar(文字型)などの定義のほかに別枠で、privatepublicと呼ばれる枠組みがあります。

メンバ変数 メンバ関数の性質

データ型

int double float char など…

アクセス指定子

private public  (protected)

一般に、「privateなメンバはクラスのスコープの外側からはアクセスができず、publicなメンバはクラスのスコープの内側と外側どちらからでもアクセス可能である。」などと言われます。スコープという難しいようなことを言っていますが、ようは、外からアクセスできるかできないかの違いです。上のサンプルコードと照らし合わせてるとわかりやすいと思います。

例えばこのようなクラス定義のなかでしたら、

class A {
private://アクセス指定子
	int x;
public: //アクセス指定子
	int y;
};

はint(整数)型でありprivateなメンバ変数です。privateは省略できますのでこう書くこともできます。

class A {
	int x;
public: //アクセス指定子
	int y;
};

ざっくり言えばclass定義の中で「public:」以前に定義されているものはprivateなメンバです。

一方yはint(整数)型でpublicなメンバ変数ということができます。この時、xはカプセル化されていてyはカプセル化されていないといえます。「保護されているかされていないか」、または「外からアクセスできるかできないかと言い換えることもできます。

protected継承にかかわる機能です。

カプセル化の狙いとメリットについて解説

カプセル化の狙いはズバリいうと不整合の防止です。わかりやすく言えば、バグの防止とか情報の保護とか言えばわかりやすいと思います。解説していきます。

メリットだけ見たい方は下にスクロールしてください。

RPGの主人公のHPをもとにわかりやすく解説

RPGの主人公はHP(Hit Point)つまりは体力を持っています。また、MAX_HP(体力の上限値)も持ち合わせていますね。

HP 800/1000

よくこんな風にHP/MAX_HPでステータスを表したりします。このときのHP=800,MAX_HP=1000になります。

戦いでHPは減っていくものです。しかし、ほとんどのRPGは、宿屋や薬草などの「回復する手段」を持っていますよね。そこで、薬草の説明欄がこういうものだったとしましょう。

薬草:HPを500回復する

よくありますよね。この薬草を使ってみたらどうでしょうか?

HP 800/1000 → HP 1300/1000

体力が上限値を超えてしまいました!これを不整合(バグ)と言ったりします。こんなことあってはいけません!(たまにこういう仕様のゲームもあったりしますが、今回はこのようなことを許したくない!)

では、「この主人公の体力を回復させたい!」と思ったときのために、HPを回復するプログラムを「カプセル化」をうまく利用して考えましょう。

カプセル化を利用したHP回復プログラム

一気にソースコード書かれると混乱する方もいらっしゃると思いますので、下のほうで1対1で解説しています。また、C++がわからない方でも読めるようにコメントを書いてあります。最初の2行は標準入出力や名前空間の定義なので、C++特有のおまじないだと思ってください。

#include<iostream>
using namespace std;

//クラスHumanの定義
class Human {
private: 

	/*--カプセル化--*/
	int HP;
	int MAX_HP;
	/*--カプセル化--*/

public:

	Human(int max_hp) { HP = max_hp; MAX_HP = max_hp; }

	//正常にHPを変更する関数
	void Change_HP(int size);

	//ステータスを表示する関数(HP/MAX_HPの形で表示)
	void Show_status() {
		cout << "HP " << HP << "/" << MAX_HP << endl; 
	}

};

//メンバ関数の定義
void Human::Change_HP(int size)
{
	int sum_HP = HP + size;

	//条件分岐
	//HPは0からMAX_HPまで
	if (sum_HP <= 0) HP = 0;
	else if (sum_HP <= MAX_HP) HP = sum_HP;
	else HP = MAX_HP;
}

int main()
{
	//HP 1000/1000の勇者作成
	Human Hero(1000);
	Hero.Show_status();

	//HP  800/1000に設定
	Hero.Change_HP(-200);
	Hero.Show_status();

	//HPに直接500を足そうとしてみる
	Hero.HP += 500; //×アクセス不可
	Hero.Show_status();

	//メンバ関数にHPを変更してもらう
	Hero.Change_HP(500); //○アクセス可
	Hero.Show_status();
}

それでは、解説していきます。

まずクラス「Human」の定義から見ていきましょう。

Humanクラス定義

//クラスHumanの定義
class Human { ... };

class(クラス)定義はこのように表しますね。今回はHumanというクラスを作ることにしました。中身を見ていきましょう。

private: 

	/*--カプセル化--*/
	int HP;
	int MAX_HP;
	/*--カプセル化--*/

public:

	Human(int max_hp) { HP = max_hp; MAX_HP = max_hp; }

	//正常にHPを変更する関数
	void Change_HP(int size);

	//ステータスを表示する関数(HP/MAX_HPの形で表示)
	void Show_status() {
		cout << "HP " << HP << "/" << MAX_HP << endl; 
	}
int HP 体力 int(整数)型のHPを定義
int MAX_HP 体力の上限値 int(整数)型のMAX_HPを定義
public: これ以降に宣言したメンバ変数やメンバ関数をクラスの外からでもアクセス可能にします。今回はHPとMAX_HPをmain文からアクセス不可にして、それ以外はmain文からでもアクセス可能(使用できるよう)にしています。
Human(int max_HP) コンストラクタ(クラス作成時の初期設定に関するプログラム)
void Change_HP(int size) HPを正常に変更するメンバ関数
void Show_status(){…} 「HP/MAX_HP」の形でステータスを表示するメンバ関数

コンストラクタの定義です。初期作成時に仮引数max_hpを受け取ります。それをHPとMAX_HPに代入します。今回ですとHero(1000)となっていますのでHP=MAX_HP=1000で作成されます。HP 1000/1000ということですね。

//メンバ関数の定義
void Human::Change_HP(int size)
{
	int sum_HP = HP + size;

	//条件分岐
	//HPは0からMAX_HPまで
	if (sum_HP <= 0) HP = 0;
	else if (sum_HP <= MAX_HP) HP = sum_HP;
	else HP = MAX_HP;
}

オブジェクト自身でHPを変更するChange_HPについての定義です。ややこしいですが、Humanクラスのメンバ関数なのでHumanのprivateなメンバ変数HPやMAX_HPにアクセスすることができます。相当重要なことです。

Human:: スコープ演算子「::」を用いてclass Humanを指定
int sum_HP HPを仮引数sizeを足した値を格納。これによって条件分岐。
if (sum_HP <= 0) HP = 0 計算結果sum_HPが0を下回ってしまうときはHP=0に設定。
else if (sum_HP <= MAX_HP) HP = sum_HP 上の条件には入らず、さらに上限値(MAX_HP)をsum_HPが超えないときはそのままHP=sum_HPに設定。
else HP = MAX_HP 上二つの条件に入らなかったとき(計算結果がMAX_HPを超えてしまったとき)HPは上限値MAX_HPに設定。

具体的に言えば、HP 5/10 をChange_HP(size)で変更するなら、

size = (-10) の場合

  1. sum_HP=HP+size=5+(-10)=(-5) NG
  2. HP = 0 (死去)

size = 2 の場合

  1. sum_HP=HP+size=5+2=7 OK
  2. HP = sum_HP = 7 (2回復)

size = 10 の場合

  1. sum_HP=HP+size=5+10=15 NG
  2. HP = MAX_HP = 10 (上限まで回復)

ということになります。そんなメンバ関数です。

main文でコンパイルエラーになる場所を確認

int main()
{
	//HP 1000/1000の勇者作成
	Human Hero(1000);
	Hero.Show_status();

	//HP  800/1000に設定
	Hero.Change_HP(-200);
	Hero.Show_status();

	//HPに直接500を足そうとしてみる
	Hero.HP += 500; //×アクセス不可
	Hero.Show_status();

	//メンバ関数にHPを変更してもらう
	Hero.Change_HP(500); //○アクセス可
	Hero.Show_status();
}
  1. HumanクラスのHeroを作成します。初期設定はHP 1000/1000です。
  2. まずはHero.Change_HP(-200)でHP 800/1000にします
  3. Hero.HP += 500 これはコンパイルエラーになります。+=は加法で、A.HPに500を足すという意味です。しかし、カプセル化によって保護されているprivateなメンバ変数なので、main文から直接アクセスすることはできません。もしこれがカプセル化がない場合だと、HP 1300/1000になってしまいますので不整合を引き起こしてしまいます。今回はこのような不整合がカプセル化によってできなくなっています
  4. Hero.Change_HP(500) これはsizeに500を受け取ったときの体力の変更プログラムですね。オブジェクト自身で体力を変更する、すなわち、Hero.HPにHeroそのものがアクセスすることになりますので、カプセル化は適用されません。よってアクセス可能となります。また、HPは上で解説した通り0~MAX_HPの範囲で変更されます。今回は上限値を超える計算になるため上限値である1000に変更されます。よってHP 1000/1000になります。

カプセル化のメリットとは?

カプセル化はこのHPが上限値を超えてしまわないようなプログラム設計からもわかる通り、情報を直接操作することをできなくして情報が壊れてしまうことを防止する役目を担っています。オブジェクト自身に適切な情報操作を委ねるというのがポイントとなります。

 終わりに

オブジェクト指向3大要素の一つともされる「カプセル化」をできる限りわかりやすく説明したつもりですが、どうでしたでしょうか?

カプセル化というのは、最初聞いたとき「なんで情報を隠す必要があるんだ?」と思いましたが、C++を学習していくうちに少しずつ理解が深まりました。

この記事を見て「なるほどなぁ、そういうことか」と思ってくださる方がいれば、とても嬉しいです。

最後まで読んでいただきありがとうございます。

オブジェクト指向のメリットとは?例に例えてわかりやすく解説!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です