C++編【言語解説】 第12章 カプセル化

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

アクセス指定子

クラスが持つ重要な機能の1つに、アクセス指定子があります。 これは、前章の Studentクラスの例の中で既に登場していて、「public:」という記述がそれです。

class Student {
	std::string  mName;   // 名前
	int          mGrade;  // 学年
	int          mScore;  // 得点
	
public:
	void SetData(std::string name, int grade, int score);
	void Print();
};

publicキーワードは、クラスか構造体の定義の中で使用できます。 「public:」という記述によって、それ以降に書かれた部分が「公開」されます
「公開」というのは、そのクラスのオブジェクトを使って、 「student.Print();」とか「student->Print();」のようにして使えるということです。 クラス定義の外部から使える(見える)ということで「公開」と表現されます。

先ほどの例では、「公開」されていないメンバ変数が3つありました。 「公開」されていないので、「student.mGrade = 2;」とか「student->mScore = 100;」のような使い方ができません。 このような状態を「非公開」であると言います。
クラスの場合、デフォルトで「非公開」であるとみなされますから、 「公開」したいメンバは、「public:」よりも後ろに記述しなければなりません

なお、明示的に「非公開」であることを表したい場合は、privateキーワードを使います。

class Student {
private:  // 以下のメンバは「非公開」
	std::string  mName;   // 名前
	int          mGrade;  // 学年
	int          mScore;  // 得点
	
public:  // 以下のメンバは「公開」
	void SetData(std::string name, int grade, int score);
	void Print();
};

アクセス指定子には、public、private の他にもう1つ、protected があります。 これは、第27章で解説します。

「公開」と「非公開」の効果を試しておきましょう。

class Test {
public:
	int mPublicValue;
	void SetValue(int value);

private:
	int mPrivateValue;
};

void Test::SetValue(int value)
{
	mPrivateValue = value;
}

int main()
{
	Test test;
	test.mPublicValue = 100;
	test.mPrivateValue = 200;  // コンパイルエラー
	test.SetValue(200);
}

「公開」されている mPublicValue へはアクセスできますが、「非公開」の mPrivateValue にはアクセスできず、コンパイルエラーとなります。 一方、SetValueメンバ関数のようなメンバ関数の内部からは、「公開」「非公開」問わず、アクセスできます。


ちなみに、好みの問題でもありますが、「公開」のメンバを先頭側に集めて、「非公開」のメンバを後ろに集めることが多いです。 これは、クラスを利用する人の立場で見れば、「非公開」のメンバには興味が無いからです(呼び出せないので)。 当サイトでも、この考え方を取って、次のような順番で書くことにします。

class Student {
public:
	void SetData(std::string name, int grade, int score);
	void Print();
	
private:
	std::string  mName;   // 名前
	int          mGrade;  // 学年
	int          mScore;  // 得点
};

また、public や private が何度も登場することは問題ありません。 そのため、極端ではありますが、次のように書くこともできます。

class Student {
public: void SetData(std::string name, int grade, int score);

private: std::string  mName;   // 名前

public: void Print();
	
private: int          mGrade;  // 学年
private: int          mScore;  // 得点
};

勿論、こんな分かり難い書き方は避けましょう。


メンバを「非公開」にする機能をうまく使うことは、OOP では非常に重要なことです。 例えば、メンバ変数score が「公開」されていると、「student.score = 500;」のように書けてしまいます。 もし、得点として正常な値が、0〜100 であるとすれば、この代入は不正です。 クラスの外部から、メンバ変数score を書き換える手段が、SetDataメンバ関数経由だけに限定されていれば、

void Student::SetData(std::string name, int grade, int score);
{
	assert(0 <= score && score <= 100);

	mName = name;
	mGrade = grade;
	mScore = score;
}

このようにチェックを入れることができます。 「あらかじめチェックを入れておくことができる」とも言えますが、「後から追加できる」ということも重要です。 「student.mScore = 100;」のような代入を至る所でされてしまった後では、このような追加は難しいです。

なお、メンバの一部を「非公開」にすることによって、データや処理内容、型といった各種要素を外部から見えないようにすることを、 OOP の用語で、カプセル化と言います。 カプセル化は、OOP の特徴の中でも、特筆すべき重要な考え方です。

setter と getter

何を「公開」して、何を「非公開」にすればいいかですが、 基本方針としては、クラスのメンバ変数は絶対に「非公開」にすることです。 とりあえず、この基本方針だけは守り通してみて下さい。

もし、「student.score = 100;」のように、メンバ変数への代入が必要なら、そのための「公開」のメンバ関数を用意します。 このような、メンバ変数の値をセットするためのメンバ関数は、セッター(setter) と呼ばれます。
また、「score = student.score;」のように、メンバ変数の値を取得したい場合は、やはりそのための「公開」のメンバ関数を用意します。 このような、メンバ変数の値を返すためのメンバ関数は、ゲッター(getter) と呼ばれます。

具体的には、次のようになります。

class Student {
public:
	void SetScore(int score);
	int GetScore();
	
private:
	int          mScore;  // 得点
};
void Student::SetScore(int score)
{
	assert(0 <= score && score <= 100);
	mScore = score;
}

int Student::GetScore()
{
	return mScore;
}

setter と getter は単なる呼び名に過ぎず、正体は普通のメンバ関数ですから、セットで作らないといけない訳ではありません。 むしろ、必要性の無いものは作らないことが重要です。
また、メンバ関数の名前としては、setter は「Set〜」とすることが多く、 getter は「Get〜」とするか単に「Score」のような名前にすることが多いです。

実際には、getter の方は更に良くできます。 そのためには、次のように、const修飾子を付加します。

class Student {
public:
	int GetScore() const;
};
int Student::GetScore() const
{
	return mScore;
}

メンバ関数の引数リストの後ろのところに「const」と書くと、constメンバ関数という特殊なメンバ関数になります。 このような const を付けると、そのメンバ関数内では、メンバ変数を書き換えられなくなります。 getter が、本当に getter としての役割に徹していれば、常に const を付けられるはずです(return するだけですから)。

もし、あるオブジェクトが const指定されていたり、constポインタがオブジェクトを指していたりする場合、 そのオブジェクトの constメンバだけしか呼び出せません。

#include <iostream>
#include "student.h"

int main()
{
	Student s1;
	const Student s2;
	Student* p1 = &s1;
	const Student* p2 = &s1;

	s1.SetScore(100);
	s2.SetScore(100);  // エラー
	p1->SetScore(100);
	p2->SetScore(100); // エラー

	std::cout << s1.GetScore() << "\n"
	          << s2.GetScore() << "\n"
	          << p1->GetScore() << "\n"
	          << p2->GetScore() << std::endl;
}

SetScoreメンバ関数は constメンバ関数でないので、const修飾子付きの s2 および、 constポインタ経由でアクセスしている p2 からは呼び出すことができません。

constメンバ関数に限らず、const修飾子は、C++プログラミングにおいては非常に重要な機能であると言えます。 const修飾子に関連する話題はかなり多くあるので、第18章で改めて取り上げます。


ところで、「student.mScore = 100;」で済むところを「student.SetScore(100);」と書いたり、 「score = student.mScore;」で済むところを「score = student.GetScore();」と書いたりすることで、 関数呼び出しのコストが増えて、効率が落ちるのが嫌だという人もいるかも知れません。
そもそも、この程度のことで目に見えてパフォーマンスが低下することはほぼ無いというのが答えですが、 仮に問題となった場合には、インライン関数(第9章)を思い出して下さい。 メンバ関数をインライン化することも可能です。

class Student {
public:
	inline void SetScore(int score);
	inline int GetScore() const;
	
private:
	int          mScore;  // 得点
};

// インライン関数の定義は、ヘッダファイル内に書く

void Student::SetScore(int score)
{
	assert(0 <= score && score <= 100);
	mScore = score;
}

int Student::GetScore() const
{
	return mScore;
}

インライン関数の定義は、ヘッダファイル側に書かなくてはいけないことに注意して下さい。
なお、メンバ関数のインライン化は次のように書くこともできます。

class Student {
public:
	inline void SetScore(int score)
	{
		assert(0 <= score && score <= 100);
		mScore = score;
	}
	inline int GetScore() const
	{
		return mScore;
	}
	
private:
	int          mScore;  // 得点
};

実はこの場合、inlineキーワードすら不要です。

class Student {
public:
	void SetScore(int score)
	{
		assert(0 <= score && score <= 100);
		mScore = score;
	}
	int GetScore() const
	{
		return mScore;
	}
	
private:
	int          mScore;  // 得点
};

たまに面倒臭がって、インライン化するつもりもなく、メンバ関数の中身を上のように書いてしまう人がいますが、 この書き方は、インライン化するという意志表示になるので注意して下さい。

クラスと構造体の違い

クラスと構造体は、"ほぼ"同一のものです。 違いは、アクセス指定子のデフォルトが、クラスは「非公開」であるのに対して、構造体は「公開」であることだけです。 従って、次の CStudentクラスと、SStudent構造体は、意味としては同一になります。

class CStudent {
public:
	void SetScore(int score);

private:
	int mScore;
};

struct SStudent {
	void SetScore(int score);  // デフォルトで public なので、これは「公開」

private:
	int mScore;
};

ただし、あくまでも別の型ですから、CStudent型の変数に SStudent型の値を代入するようなことはできません。

あえてクラスと構造体を使い分けるとすれば、C言語的な構造体が必要な場合には struct を使い、 そうでなければ class を使うということです。 つまり、「複数の変数の集合体」というだけなら、struct にしておくということです。
前に、クラスのメンバ変数は必ず「非公開」にすると書きましたが、 C言語的な構造体の意味合いで struct を使うのなら、例外的にメンバ変数を「公開」にしても良いでしょう。


練習問題

問題@ setter と getter を両方用意する代わりに、メンバ変数のアドレスを返すメンバ関数を用意することは、 良いアイディアでしょうか?

問題A 「非公開」なメンバ関数は、どのような用途に使えるでしょうか?

問題B 次の「正方形」を表す構造体について、カプセル化の観点から、問題点を指摘して下さい。

// 正方形を表す構造体
struct Square {
	int			mSide;    // 1辺の長さ
	int			mArea;    // 面積
};


解答ページはこちら

参考リンク

更新履歴

'2014/4/24 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ