誰があなたのデータに触れられるか
最初のクラスを書いたとき、あなたはおそらくすべてのメンバーを外の世界にさらけ出したことでしょう。それでも動きますが、クラスが存在する主な理由の 1 つを捨てています。すなわち カプセル化 — クラスの内部状態を隠し、プログラムの残りの部分が制御された表面を通じてのみそれとやり取りできるようにすることです。アクセス指定子は、その境界を引くための手段です。
ちょうど 3 つあります。public、private、protected です。それぞれが後続のメンバーにラベルを付け、そのラベルがどのコードに読み書きを許すかを決めます。これを正しく行えばクラスは自らのルールを強制します。間違えれば、どこかのどんなバグでもオブジェクトの状態を壊しかねません。
3 つの指定子
指定子はキーワードの後にコロンを付けたものです。その後に宣言されたすべてのメンバーは、次の指定子までそのアクセスレベルに属します。
最後の行のコメントを外すと、コンパイラはビルドを拒否します。balance は private なので、main はそれに直接手を出せません。これが狙いです。残高を変更する唯一の方法は deposit を通すことであり、つまり後から検証(マイナスの入金を禁止、ロギング、上限)を 1 か所 に追加でき、それが常に適用されると信頼できるのです。
完全な内訳は次のとおりです。
// どこからアクセスできるか...
// public どこからでも(そのオブジェクトを持つ任意のコード)
// private 同じクラス自身のメンバーのみ(+ friend)
// protected 同じクラスのメンバー および 派生クラス(+ friend)
class と struct: デフォルト
指定子ブロックは好きなだけ、好きな順序で書けます。最初の指定子を書く 前 のメンバーが何になるかは、class を使ったか struct を使ったかで決まります。
classでは、メンバーはデフォルトでprivateです。structでは、メンバーはデフォルトでpublicです。
このデフォルトこそが、2 つのキーワード間の 唯一の 言語レベルの違いです。struct も class と同じようにメソッド、コンストラクタ、private セクションを持てます。
慣例として、公開データを束ねるだけのものには struct を、振る舞いと隠された状態が欲しいときには class を使います。ただしコンパイラはそれを強制しません。違うのはデフォルトだけです。
ゲッターとセッターによるカプセル化
日常的なパターンはこうです。データは private セクションに入れ、public メソッドが制御されたアクセスを与えます。読み取り専用の ゲッター は値を返し、セッター は代入する前に検証します。ここで private が報われます。
celsius が private であるため、無効な値を忍び込ませる方法はありません。あらゆる書き込みは不変条件を守る setCelsius を通さなければなりません。ゲッターが const でマークされていることに注目してください。これらはオブジェクトを変更しないと約束しているので、const Temperature オブジェクトに対しても呼び出せます。
protected と継承
protected が意味を持つのは、継承が登場したときだけです。外部コードに対しては private のように振る舞いますが、派生クラス は アクセスできます。サブクラスが正当に必要とするものの、外部にはやはり持たせるべきでないメンバーに使います。
初心者によくある間違いは、「サブクラスが必要とするかもしれないから」とすべてのデータメンバーに protected を使うことです。これはクラスのコントラクトを密かに広げてしまいます。今やすべてのサブクラスがそのフィールドに依存しうるため、自由に変更できなくなります。private を優先し、派生クラスが本当にアクセスを必要とするときだけ protected に格上げしましょう。
friend という抜け道
ときには、外部の関数やクラス 1 つが正当にあなたの内部を見る必要があります。典型的な例は、メンバーにできない << のような演算子です。friend キーワードは、その名指しした 1 つの存在にあなたの private および protected メンバーへのアクセスを与え、それ以外には何も与えません。
friend は意図的で外科的な例外です。クラス自身が誰を信頼するかを正確に名指しするので、外部から自分自身にアクセス権を与えることはできません。控えめに使いましょう。たくさんの friend を追加していることに気づいたら、そもそもメンバーが private であるべきではなかったか、設計を見直す必要があるかのどちらかでしょう。
避けるべきよくある間違い
- すべてのメンバーを
publicにする。 簡単に感じますが、カプセル化がもたらす検証と不変条件をすべて失います。デフォルトはprivateデータとpublicメソッドにしましょう。 classのデフォルトを忘れる。class Foo { int x; };はxを private にするので、foo.x = 5はコンパイルできません。単なるデータの束のつもりなら、structを使うかpublic:ラベルを追加しましょう。protectedを使いすぎる。 これはprivateより弱い境界で、継承のときだけ関係します。あちこちで使うと、変更したいかもしれないフィールドにサブクラスを結びつけてしまいます。privateをセキュリティ機能と期待する。 これは偶発的なアクセスを防ぐ コンパイル時 のルールであって、暗号化ではありません。バイトは依然としてメモリ上にあります。privateはきれいな設計のためのものであり、秘匿のためではありません。
次へ: 構造体
これで、struct が実はメンバーがデフォルトで public なだけの class であることがわかりました。次のページ、構造体では、この「デフォルトで public」というデフォルトがまさに望ましい場面 — 関連する値をまとめる軽量な集約 — と、struct が機能完備なクラスと並んで慣用的な C++ でどう使われるかを掘り下げます。
よくある質問
C++ における public、private、protected の違いは何ですか?
public メンバーはどこからでもアクセスできます。private メンバーは同じクラスの内部(およびその friend)からのみアクセスできます。protected は private に似ていますが、加えて派生クラスからもそのメンバーにアクセスできます。慣例として、データは private に保ち、振る舞いは public メソッドを通じて公開します。
C++ ではメンバーはデフォルトで private ですか?
class ではそうです。アクセス指定子を書くまではすべてが private です。struct ではデフォルトが public です。この 1 つのデフォルトこそが、C++ における class と struct の唯一の本質的な違いです。どちらもメソッド、コンストラクタ、アクセス指定子を持てます。
C++ の friend キーワードは何をしますか?
friend は特定の関数やクラス 1 つに、あなたの private および protected メンバーへのアクセス権を与えます。これはカプセル化に対する意図的で限定的な例外です。クラスが誰を信頼するかを正確に名指しするため、アクセスが暗黙的に与えられることは決してありません。