クラス設計では、メンバ変数への直接アクセスを避けること(カプセル化)が基本的な考え方です。変数を public にして外部から自由に操作できるようにすると、予期しない値の変更や不正な状態を引き起こす可能性があります。
そのため、変数を private にし、必要に応じてゲッター(getter)やセッター(setter)を通して安全にアクセスさせる、という定番の設計手法があります。
この資料では、クラスの値を安全に扱うための3つの考え方、ゲッター(getter)、セッター(setter)、そしてプロパティ(property)について学びます。
これらを使うことで、クラスの内部データを保護し、不正な値の代入を防ぐことができます。
// 09_2_getter_setter.cpp
#include <iostream>
using namespace std;
class Player {
private:
int hp;
public:
Player(int initHp) : hp(initHp) {}
// ゲッター
int getHp() const {
return hp;
}
// セッター
void setHp(int value) {
if (value >= 0) hp = value; // 不正値を防ぐ
}
};
int main() {
Player p(100);
cout << "初期HP: " << p.getHp() << endl;
p.setHp(80);
cout << "攻撃を受けた後のHP: " << p.getHp() << endl;
p.setHp(-50); // 無効な値
cout << "不正な値を設定後のHP: " << p.getHp() << endl;
return 0;
}
実行結果(例)
初期HP: 100 攻撃を受けた後のHP: 80 不正な値を設定後のHP: 80
hp を直接外部から変更できないようにすることで、データの安全性を保つ。| 目的 | 説明 |
|---|---|
| 安全性 | 不正な値(例:負のHP)の代入を防ぐことができる |
| 拡張性 | 将来、値を変更する処理を追加しても、呼び出し側を変えずに済む |
| 可読性 | getHp() や setHp() の名前で、意図が明確になる |
近年の言語(C# など)では、ゲッター・セッターをより自然に使えるようにしたプロパティという仕組みがよく使われています。
C++ では正式な機能としては存在しませんが、同じ考え方を理解しておくと、他の言語や将来の C++ 仕様を学ぶときに役立ちます。
// C#のプロパティ例
public class Player {
private int hp;
public int Hp {
get { return hp; } // 読み出し時に呼ばれる
set {
if (value >= 0) hp = value; // 不正値を防ぐ
}
}
}
void Main() {
Player p = new Player();
p.Hp = 100; // setterを通じて値を設定
Console.WriteLine(p.Hp); // getterを通じて値を取得
p.Hp = -50; // 不正な値は無視される
Console.WriteLine(p.Hp); // 100 のまま
}
Hp に直接アクセスしているように見えますが、内部では get / set が自動的に呼ばれています。C++ にはプロパティ構文はありませんが、次のように明示的に関数を書くことで同様の仕組みを表現できます。
// 09_2_property_like.cpp
#include <iostream>
using namespace std;
class Player {
private:
int hp;
public:
int getHp() const { return hp; }
void setHp(int value) { hp = value; }
};
const int& n の & は「参照型」を表します。
コピーを作らず元の値を直接参照する仕組みで、大きなデータを効率よく扱うために使います。
詳しくは 18章(拡張for文とイテレータ) で扱います。
// 09_2_getter_setter.cpp
#include <iostream>
using namespace std;
class Player {
private:
int hp;
public:
Player(int initHp) : hp(initHp) {}
int getHp() const {
return hp;
}
void setHp(int value) {
if (value >= 0) hp = value;
}
};
int main() {
Player p(100);
cout << "初期HP: " << p.getHp() << endl;
p.setHp(80);
cout << "攻撃後のHP: " << p.getHp() << endl;
p.setHp(-50);
cout << "不正値設定後のHP: " << p.getHp() << endl;
return 0;
}
09x_01_GetSet の 09_2_getter_setter.cpp をコピーして 09x_02_GetSetMod に貼り付け、次の変更を加えてみましょう:
name(string型)メンバ変数を追加し、getName() と setName() を実装するname.empty())のときは設定を無視するようにするmain() で名前を設定・取得する処理を追加する次のコードを実行したとき、3行目の出力はどれか。
Player p(100); p.setHp(80); p.setHp(-50); // 不正な値 cout << p.getHp();
(セッターは if (value >= 0) hp = value; で実装されているとする)
value >= 0 が false(-50 は負)なので、代入されない。直前に設定した 80 がそのまま保持される。カプセル化による不正値防御の典型例。
ゲッターに const を付ける目的として正しいものはどれか。
int getHp() const { return hp; }
const は、その関数がオブジェクトの状態(メンバ変数)を変更しないことを宣言する。const オブジェクトからでも呼び出せるようになる。
メンバ変数を private にしてゲッター・セッターを使う最大のメリットはどれか。
if (value >= 0) のようなバリデーションを加えることで、外部から誤った値を設定されてもクラスが正しい状態を維持できる。
次のコードのうち、カプセル化として正しいものはどれか。
hp を private にして、アクセスはゲッター・セッターのみに限定するのがカプセル化の正しい実装。選択肢アとエは hp が外部から直接変更できてしまう。選択肢ウはデフォルトで private だがアクセス手段がない。