C++ではクラスの中身を誰からどこまで見せるかを決めるためにアクセス指定子を使います。
本記事ではpublic と private を使ったデータ隠蔽の基本を、初心者の方にも分かりやすい流れで解説します。
後半には実行できるサンプル(BankAccount)を用意し、設計のコツやよくあるミスも具体的に示します。
C++のアクセス指定子の基本(public, private)
アクセス指定子とは
アクセス指定子は、クラスのメンバ(メンバ変数・メンバ関数)へ誰がアクセスできるかを決めるキーワードです。
C++の代表的な指定子はpublic
とprivate
です。
意図しない外部からの直接操作を防ぐことで、プログラムの安全性と保守性を高めます。
以下に簡潔な比較表を示します。
アクセス指定子 | クラス外部からのアクセス | 同一クラス内からのアクセス |
---|---|---|
public | 可能 | 可能 |
private | 不可 | 可能 |
これは内部実装を保護しつつ、必要な操作だけを公開するために重要です。
public の意味と使い方
public
は外部から自由に使える「公開インターフェース」を定義するときに利用します。
ユーザが呼び出すべきメソッド(関数)や、どうしても公開したい定数などをここに置きます。
外部に約束する「使い方の窓口」が public です。
// public を使ったシンプルな例
#include <iostream>
#include <string>
class Greeter {
public:
// 外部から呼び出せるメンバ関数(公開インターフェース)
void sayHello(const std::string& name) const {
std::cout << "Hello, " << name << "!" << std::endl;
}
};
int main() {
Greeter g; // クラスからオブジェクトを作成
g.sayHello("C++"); // public の関数は外部から呼べる
}
Hello, C++!
private の意味と使い方
private
は、そのクラスの外から触れられたくない実装の詳細や状態を隠すために使います。
外部から直接触れさせないことで、不正な状態やバグの温床を防ぐ狙いがあります。
// private を使った基本形
#include <iostream>
class Counter {
private:
int value_ {0}; // 外部から直接は触れない(クラス内部の実装詳細)
public:
void increment() { // 操作は public を通じて提供する
++value_;
}
int value() const { // 状態の観察は必要最小限に
return value_;
}
};
int main() {
Counter c;
c.increment();
c.increment();
std::cout << c.value() << std::endl; // OK: public の関数経由
// c.value_ = 100; // コンパイルエラー: private は外部からアクセス不可
}
2
外部から変更されては困るprivate メンバを直接触る設計は避けてください。
class のデフォルトは private
C++のclass
では、アクセス指定子を何も書かない場合のデフォルトはprivateです。
つまり、何も指定しないで宣言したメンバは外からアクセスできません。
対してstruct
のデフォルトは public です(詳細は別記事で扱います)。
class Sample {
int x; // 何も指定していないので private (class はデフォルトで private)
public:
Sample() : x(0) {}
int get() const { return x; }
};
メンバ変数とメンバ関数の可視性
アクセス指定子は、指定した位置から次の指定子が現れるまでのメンバに適用されます。
つまり、ブロックや波括弧で区切るのではなく「宣言の並びに効く」イメージです。
可視性は「メンバ変数」「メンバ関数」のどちらにも同様に適用できます。
class Layout {
private:
int width_; // ここから...
int height_; // ...ここまで private
public:
void resize(int w, int h) { width_ = w; height_ = h; } // ここから public
int area() const { return width_ * height_; }
};
データ隠蔽の目的とメリット
C++でデータ隠蔽が必要な理由
C++はパフォーマンス志向の言語であり、柔軟さの反面「なんでもできてしまう」面があります。
データ隠蔽は、柔軟さの副作用である破壊的変更や不正状態の発生を抑えるための基本技法です。
クラスの利用者には必要最小限の機能だけを見せ、内部構造は自由に変更できるようにします。
不正な値や状態を防ぐ
外部から値を直接書き換えられると、0未満になってはいけない残高がマイナスになるなど整合性の破壊が起きます。
private で値を守り、public
な操作(メソッド)内でチェックを行うことで、クラス内変数の不変条件を維持できます。
インターフェースと実装を分ける
利用者が知るべきは「何ができるか」だけです。
内部の配列やデータ構造の選択は実装の詳細であり、公開インターフェースと実装を分離しておくと、後から中身を配列からハッシュに変えても外部コードに影響を与えません。
変更に強い安全な設計にする
データ隠蔽は変更の波及を局所化します。
壊れにくく修正しやすいコードになるため、機能追加やリファクタリング時のコストを抑えられます。
これは中長期の開発では大きな差になります。
初心者向けの使い方パターン
メンバ変数は基本 private にする
迷ったらまずは全メンバ変数を privateにしてください。
必要になった時だけ public の窓口を用意する方が、安全で設計の見通しも良くなります。
操作は public のメンバ関数で提供する
利用者が行いたい操作だけを public で公開します。
例えば「入金する」「残高を確認する」のように、意味のある行為としての関数を提供します。
これにより、クラスの責務が明確になります。
getter と setter は必要最小限にする
なんでもかんでも getter/setter を公開すると、結局なんでもできる状態になります。
実現したい操作を関数として用意する方が、整合性チェックを入れやすく、バグも減ります。
まず private で始めて必要なら public を開く
設計初期は厳しめに閉じ、本当に必要になったところだけ最小限を公開する方針が堅実です。
公開はいつでもできますが、一度公開したものを後から閉じると互換性問題が起きやすいです。
サンプルクラスの構成例(BankAccount)
以下は、private と public を使ってデータ隠蔽を実践するサンプルです。
コンストラクタで初期化し、デストラクタで終了メッセージを出すようにしています。
残高は外から直接書き換えられず、必ず関数を経由します。
#include <iostream>
#include <string>
#include <stdexcept>
#include <iomanip>
class BankAccount {
private:
std::string owner_; // 口座名義(外部から直接変更させない)
long long balance_; // 残高(常に0以上を維持する不変条件)
// 内部用の検証関数(外部に見せない実装の都合)
void ensureNonNegative(long long amount) const {
if (amount < 0) {
throw std::invalid_argument("negative amount is not allowed");
}
}
public:
// コンストラクタ(初期化処理)
BankAccount(const std::string& owner, long long initial)
: owner_(owner), balance_(0) {
deposit(initial); // 初期入金もルールに従って処理する
std::cout << "Account created for " << owner_ << std::endl;
}
// デストラクタ(後片付け)
~BankAccount() {
std::cout << "Account closed for " << owner_ << std::endl;
}
// 入金操作(public の窓口)
void deposit(long long amount) {
ensureNonNegative(amount);
balance_ += amount;
}
// 出金操作(整合性チェックを内部で担保)
bool withdraw(long long amount) {
ensureNonNegative(amount);
if (amount > balance_) {
return false; // 残高不足
}
balance_ -= amount;
return true;
}
// 観察用の最小限のアクセサ
long long balance() const {
return balance_;
}
std::string owner() const {
return owner_;
}
// 口座情報を表示する便利関数
void print() const {
std::cout << "[Owner] " << owner_
<< " [Balance] " << balance_ << std::endl;
}
};
int main() {
try {
BankAccount a("Alice", 1000); // 口座作成と初期入金
a.print();
a.deposit(500); // 入金は必ず関数を通す
std::cout << "After deposit: " << a.balance() << std::endl;
if (!a.withdraw(2000)) { // 残高不足は false を返す
std::cout << "Withdraw failed: insufficient funds" << std::endl;
}
a.withdraw(1200); // 有効な出金
a.print();
// a.balance_ = 999999; // コンパイルエラーになるため禁止(データ隠蔽)
// a.owner_ = "Mallory"; // これも禁止
// 不正な値を渡すと例外が投げられる
// a.deposit(-10);
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
}
Account created for Alice
[Owner] Alice [Balance] 1000
After deposit: 1500
Withdraw failed: insufficient funds
[Owner] Alice [Balance] 300
Account closed for Alice
この例では、balance_ と owner_ は privateで守られ、外部からはdeposit
やwithdraw
などの public 関数を通してのみ変更できます。
これにより、常に整合性を満たす経路からしか状態が変わらないことが保証されます。
よくあるミスとチェックポイント
public にし過ぎる
必要性が曖昧なままメンバを public にすると、外部コードに強く依存され、後から変えにくくなります。
公開は最小限が原則です。
フィールドを直接公開してしまう
変数を public にすると無制限に書き換えられ、不変条件の崩壊が起きます。
代わりに、意味のある操作関数を公開し、その内部で検証しましょう。
アクセス指定子の書き忘れ
class のデフォルトは private です。
どこからどこまでが public かを明示し、意図しない非公開・公開を避けましょう。
宣言の塊ごとにpublic:
やprivate:
をはっきり書くと読みやすくなります。
テストのために設計を崩さない
テストの便宜のために本来 private であるべきものを public にするのは本末転倒です。
公開インターフェースを通したテストを優先し、必要ならテスト専用のヘルパや依存の分離を検討してください。
まとめ
本記事では、C++のpublic
とprivate
を使ったデータ隠蔽の基本を解説しました。
メンバ変数はまず private、操作は public のメンバ関数で提供、getter/setter は最小限という原則を守れば、堅牢で変更に強いコードになります。
データ隠蔽はコードの安全性だけでなく、将来の拡張やリファクタリングを容易にします。
まずは小さなクラスから、この設計習慣を身に付けていきましょう。