C++におけるプログラミングにおいて、オブジェクト指向の三大要素の一つである「カプセル化」を実現するために欠かせないのがアクセス修飾子です。
クラス内部のデータや関数をどの範囲まで公開するかを制御することは、堅牢でメンテナンス性の高いコードを記述するための第一歩となります。
アクセス修飾子を正しく理解していないと、意図しない場所から変数が書き換えられたり、クラス間の依存関係が複雑になりすぎたりするリスクがあります。
本記事では、public、private、protectedの基本的な役割から、継承時のアクセス制御ルールの詳細まで、実務で役立つ知識を詳しく解説します。
C++におけるアクセス修飾子の基本概念
アクセス修飾子とは、クラスのメンバ(変数や関数)に対して、どこからアクセスできるかを制限するためのキーワードです。
C++には「public」「private」「protected」の3種類が存在し、それぞれ異なるアクセス範囲を持っています。
適切にアクセス制限を行うことで、オブジェクトの内部状態を保護し、外部からは公開されたインターフェースのみを通じて操作を行うように誘導できます。
これを「情報の隠蔽(Information Hiding)」と呼び、バグの混入を防ぐ重要なテクニックとなります。
public(公開アクセス)
public修飾子が指定されたメンバは、プログラムのどこからでも自由にアクセスすることが可能です。
主に、外部のクラスや関数から呼び出すための「操作窓口(インターフェース)」となる関数に対して使用されます。
メンバ変数をpublicにすることも可能ですが、外部から値を直接書き換えられてしまうため、推奨されるケースは限られます。
private(非公開アクセス)
private修飾子が指定されたメンバは、そのメンバが定義されているクラスの内部からのみアクセスできます。
継承先のクラス(派生クラス)からであっても、直接アクセスすることは許可されません。
クラスの内部状態を保持するメンバ変数は、原則としてprivateに設定し、外部からの干渉を遮断するのがC++の定石です。
protected(保護アクセス)
protected修飾子は、privateと似ていますが、継承関係にある派生クラスからのアクセスを許可するという点が異なります。
クラスの外部(インスタンスを利用する側)からはアクセスできませんが、そのクラスを拡張して新しいクラスを作る際には利用できるという性質を持ちます。
基底クラスで定義した共通の処理やデータを、子クラスで再利用したい場合に非常に便利な機能です。
アクセスレベルの比較一覧
各アクセス修飾子の違いを整理するために、アクセス権限を表にまとめました。
| アクセス場所 | public | protected | private |
|---|---|---|---|
| 自クラス内 | ○ 可能 | ○ 可能 | ○ 可能 |
| 派生クラス(子クラス)内 | ○ 可能 | ○ 可能 | × 不可 |
| クラス外(インスタンス経由) | ○ 可能 | × 不可 | × 不可 |
この表からわかる通り、privateが最も厳格であり、publicが最も開放的です。
設計の基本は、「可能な限りアクセス権を最小限に絞る」ことであることを意識しましょう。
アクセス修飾子の具体的な記述方法
実際のコードでどのようにアクセス修飾子を記述するのか、基本的な構文を確認します。
C++では、ラベル(public:など)を記述した以降のすべてのメンバにその設定が適用されます。
#include <iostream>
#include <string>
class User {
private:
// クラス内部でのみ使用する変数
int id;
std::string secret_key;
protected:
// 派生クラスでも使用可能な変数
std::string username;
public:
// 外部からアクセス可能な関数(コンストラクタ)
User(int u_id, std::string name) : id(u_id), username(name) {
secret_key = "init_hash_123";
}
// 外部から名前を取得するための公開メソッド
void print_info() {
std::cout << "User: " << username << " (ID: " << id << ")" << std::endl;
}
};
int main() {
User user1(101, "Alice");
// publicメンバなので呼び出し可能
user1.print_info();
// privateメンバに直接アクセスしようとするとコンパイルエラーになる
// user1.id = 102;
return 0;
}
User: Alice (ID: 101)
この例では、idやsecret_keyは厳重に保護されており、main関数などの外部から直接書き換えることはできません。
これにより、Userオブジェクトの整合性が保たれる仕組みになっています。
クラス(class)と構造体(struct)のデフォルトアクセス権
C++において、classとstruct(構造体)の機能的な違いはほとんどありませんが、唯一の重要な違いが「デフォルトのアクセス修飾子」です。
classキーワードで定義した場合、修飾子を明示しないメンバはすべてデフォルトで privateとなります。
一方で、structキーワードで定義した場合、デフォルトではすべて publicとして扱われます。
class MyClass {
int x; // デフォルトで private
};
struct MyStruct {
int x; // デフォルトで public
};
int main() {
MyClass c;
// c.x = 10; // コンパイルエラー:privateメンバへのアクセス
MyStruct s;
s.x = 10; // OK:publicメンバなのでアクセス可能
return 0;
}
一般的に、データ保持を主目的とする単純なデータ構造にはstructを使い、複雑な振る舞い(メソッド)を持つオブジェクトにはclassを使うという使い分けが行われます。
継承時におけるアクセス修飾子の役割
C++のアクセス修飾子の理解で最も難解なのが、継承時におけるアクセス指定(Inheritance Access Specifiers)です。
クラスを継承する際、class Derived : public Baseのように、基底クラス名の前にアクセス修飾子を記述します。
これは「基底クラスのメンバを、派生クラスにおいてどの程度の公開レベルで引き継ぐか」を決定するものです。
1. public継承(最も一般的)
public継承は、基底クラスの性質をそのままの公開レベルで引き継ぐ方式です。
- 基底クラスの
publicメンバ → 派生クラスでもpublic - 基底クラスの
protectedメンバ → 派生クラスでもprotected - 基底クラスの
privateメンバ → アクセス不可(存在はするが直接触れない)
これは「is-a関係(派生クラスは基底クラスの一種である)」を表現する際に使われる、最も標準的な継承方法です。
2. protected継承
protected継承を行うと、基底クラスの公開設定が一部格下げされます。
- 基底クラスの
publicメンバ → 派生クラスではprotected - 基底クラスの
protectedメンバ → 派生クラスでもprotected - 基底クラスの
privateメンバ → アクセス不可
外部に対しては一切の機能を非公開にしたいが、さらにその下の孫クラスには機能を引き継がせたいという特殊な設計で用いられます。
3. private継承
private継承は、基底クラスのすべてのメンバを、派生クラス内だけの秘密の機能として取り込む方式です。
- 基底クラスの
publicメンバ → 派生クラスではprivate - 基底クラスの
protectedメンバ → 派生クラスではprivate - 基底クラスの
privateメンバ → アクセス不可
これは「has-a関係(実装を利用するための継承)」に近い意味合いを持ち、外部からは継承関係にあること自体を隠したい場合に使用されます。
継承とアクセスの動作確認コード
実際に継承によるアクセスレベルの変化をコードで確認してみましょう。
#include <iostream>
class Base {
public:
int pub_val = 1;
protected:
int prot_val = 2;
private:
int priv_val = 3;
};
// public継承の例
class PublicDerived : public Base {
public:
void access_check() {
std::cout << pub_val << std::endl; // OK
std::cout << prot_val << std::endl; // OK
// std::cout << priv_val << std::endl; // エラー
}
};
// private継承の例
class PrivateDerived : private Base {
public:
void access_check() {
std::cout << pub_val << std::endl; // クラス内部ならOK
std::cout << prot_val << std::endl; // クラス内部ならOK
}
};
int main() {
PublicDerived pub_d;
std::cout << pub_d.pub_val << std::endl; // OK
PrivateDerived priv_d;
// std::cout << priv_d.pub_val << std::endl; // エラー: 継承時にprivate化されている
return 0;
}
このように、継承時のアクセス指定子によって、外部から見たときの「見え方」が劇的に変わる点に注意が必要です。
適切な使い分けのベストプラクティス
アクセス修飾子をどのように使い分けるべきか、設計上のガイドラインを紹介します。
メンバ変数は原則 private にする
クラスのデータメンバはすべてprivateにし、必要に応じてpublicなGetter/Setterメソッドを提供しましょう。
これにより、変数の値が変更される際にバリデーション(妥当性チェック)を入れたり、内部の実装を後から変更したりすることが容易になります。
「直接変数を触らせない」という制約が、将来的な不具合修正のコストを大幅に下げてくれます。
継承を前提とするなら protected を検討する
基底クラスを作成し、その機能を拡張した派生クラスを作る予定がある場合は、重要な内部メソッドや共通変数をprotectedにします。
ただし、protectedメンバを増やしすぎると、派生クラスが基底クラスの内部実装に依存しすぎる「壊れやすい基底クラス(Fragile Base Class)」問題を引き起こす可能性があります。
可能な限り private で済ませられないかを検討した上で、必要最小限の要素のみをprotectedにするのが賢明です。
インターフェースのみを public にする
利用者が知るべきなのは「そのクラスで何ができるか」であって、「どのように実現しているか」ではありません。
外部に公開するpublic関数は、最小限かつ直感的に使えるものだけに絞り込みましょう。
これにより、クラスの使い方を簡潔に保ち、誤用を防ぐことができます。
アクセス制限を回避する「friend」キーワード
時には、特定の外部関数やクラスに対してだけ、特別なアクセス権を与えたい場合があります。
そのような時に使用するのがfriendキーワードです。
friendとして指定された関数やクラスは、対象クラスのprivateおよびprotectedメンバに直接アクセスできるようになります。
class Secret {
private:
int data = 42;
// 特定の関数にアクセスを許可
friend void reveal_secret(const Secret& s);
};
void reveal_secret(const Secret& s) {
// privateメンバにアクセス可能
std::cout << "Secret is: " << s.data << std::endl;
}
ただし、friendはカプセル化を意図的に壊す機能であるため、乱用は避けるべきです。
演算子オーバーロード(<<演算子の実装など)など、特定のケースに限定して使用するのが一般的です。
まとめ
C++のアクセス修飾子は、プログラムの整合性を守り、複雑さを管理するための非常に強力なツールです。
publicは外部への窓口、privateはクラス内の秘密、protectedは家族内(継承関係)の共有財産、というイメージを持つと理解しやすいでしょう。
また、継承時におけるアクセス制限の格下げルールを把握しておくことは、大規模なライブラリ設計やフレームワーク利用において不可欠な知識です。
「最小特権の原則」に従い、不必要な公開を避けることで、安全で拡張性の高いC++プログラムを構築していきましょう。
まずはすべての変数をprivateにすることから始め、設計上の必要性に応じて慎重にアクセス権を広げていく習慣を身につけてください。
