C++入門|publicとprivate

publicは外から使える公開技、privateはクラスの内側だけで扱う秘密の力。アクセス指定子を使い分ければ、戦士オブジェクトの大切なデータを安全に守れます。

ここまで、C++のクラスを使って、Warriorクラスのような「戦士の設計図」を作ってきました。

クラスには、戦士の名前、戦闘力、気の倍率のようなデータを表すメンバ変数と、修行する、状態を表示する、戦闘力を計算するといった動作を表すメンバ関数を持たせることができます。

ここで大切になってくるのが、アクセス指定子です。

アクセス指定子とは、クラスのメンバ変数やメンバ関数を、どこから使えるようにするかを決めるための指定です。

これまでのサンプルでは、publicを使って、main.cppからメンバ変数やメンバ関数にアクセスしてきました。

warrior.basePower = 1500;
warrior.showStatus();

このように外から直接使えたのは、それらのメンバがpublicとして宣言されていたからです。

しかし、実際のプログラムでは、すべてのデータを外から自由に変更できるようにしてしまうと危険な場合があります。

ドラゴンボール風にたとえるなら、戦士の名前や公開されている戦闘力は、スカウターで外から確認できてもよい情報です。
一方で、奥義の発動条件や隠された潜在能力のような情報は、戦士の内側でだけ扱いたい秘密の力です。

このように、外から使えるものと、クラスの内側だけで使うものを分けるために、publicやprivateを使います。

アクセス指定子意味ドラゴンボール風のイメージ
publicどこからでもアクセスできる外から使える公開技、表示パネル
private同じクラスの中からだけアクセスできる戦士の内側にある秘密の力
protected同じクラスや継承先からアクセスできる流派を受け継ぐ者だけが扱える技

protectedは、継承を学ぶときに詳しく扱います。
ここではまず、publicとprivateの違いをしっかり理解していきましょう。

図:publicは外から使える入口、privateは内側だけの秘密領域

この図が示していること

この図では、publicとprivateのアクセス範囲の違いを表しています。

publicに置かれたメンバは、main.cppのようなクラスの外側からアクセスできます。
一方、privateに置かれたメンバは、クラスの外側から直接アクセスできません。

privateメンバは、同じクラスのメンバ関数の中からだけ使えます。
つまり、外から直接触らせたくない大切なデータや内部処理を守るために使います。

アクセス指定子とは何か

アクセス指定子は、クラスのメンバに対して「どこから使ってよいか」を決めるための指定です。

C++のクラスでは、次のような形でアクセス指定子を書きます。

class クラス名 {
public:
    // 外からアクセスできるメンバ

private:
    // クラス内からだけアクセスできるメンバ
};

publicの下に書いたメンバは、クラスの外側から使えます。

privateの下に書いたメンバは、クラスの外側からは使えません。
ただし、同じクラスのメンバ関数の中からは使えます。

書く場所クラス外からアクセスクラス内からアクセス
publicできるできる
privateできないできる

ここでいうクラス外とは、たとえばmain.cppのmain関数のような場所です。

一方、クラス内とは、BattleWarrior::startTrainingのように、そのクラスに属するメンバ関数の中のことです。

publicのイメージ

publicは、外から使えるメンバを置く場所です。

たとえば、戦士の公開されている戦闘力openPowerや、修行を開始するstartTrainingは、main.cppから使えるようにしたいとします。

その場合、publicに書きます。

public:
    int openPower;
    void startTraining();

こうしておくと、main.cppから次のようにアクセスできます。

fighter.openPower = 100;
fighter.startTraining();

ドラゴンボール風にたとえるなら、publicは修行場の受付口です。

司令室から「公開戦闘力を設定する」「修行を開始する」といった操作ができます。

privateのイメージ

privateは、クラスの内側だけで使うメンバを置く場所です。

たとえば、隠された潜在能力hiddenPowerや、その潜在能力を呼び起こすawakenHiddenPowerは、外から直接いじらせたくないとします。

その場合、privateに書きます。

private:
    int hiddenPower;
    void awakenHiddenPower();

privateに書いたメンバは、main.cppから次のようには使えません。

fighter.hiddenPower = 500;
fighter.awakenHiddenPower();

このように書くと、ビルドエラーになります。

なぜなら、hiddenPowerもawakenHiddenPowerもprivateメンバだからです。

ただし、同じBattleWarriorクラスのメンバ関数の中からなら使えます。

void BattleWarrior::startTraining() {
    hiddenPower = 300;
    awakenHiddenPower();
}

ドラゴンボール風に言えば、hiddenPowerは戦士の内側に眠る秘密の力です。
外部の司令室から勝手に書き換えることはできません。

でも、戦士自身の修行処理の中では、その力を扱うことができます。

publicとprivateを使ったサンプルプログラム

ここからは、publicとprivateの違いを確認できるサンプルプログラムを見ていきます。

今回のプログラムでは、BattleWarriorクラスを作ります。

BattleWarriorクラスには、外から設定できる公開戦闘力openPowerと、外からは直接触れない隠し戦闘力hiddenPowerを持たせます。

メンバアクセス指定子内容
openPowerpublic外から設定できる公開戦闘力
startTrainingpublic外から呼び出せる修行開始メソッド
hiddenPowerprivateクラス内だけで扱う隠し戦闘力
awakenHiddenPowerprivateクラス内だけで呼び出す潜在能力解放メソッド

ファイル構成は次の3つです。

ファイル役割
warrior_access.hBattleWarriorクラスの宣言を書く
warrior_access.cppメンバ関数の定義を書く
main.cppインスタンスを生成してpublicメンバを使う

BattleWarriorクラスの宣言

プロジェクト/ファイル名: Chap3_03/warrior_access.h

#ifndef _WARRIOR_ACCESS_H_
#define _WARRIOR_ACCESS_H_

class BattleWarrior {
public:
    // 外からアクセスできる公開戦闘力
    int openPower;

private:
    // クラス内だけで扱う隠し戦闘力
    int hiddenPower;

public:
    // 外から呼び出せる修行開始メソッド
    void startTraining();

private:
    // クラス内だけで呼び出す潜在能力解放メソッド
    void awakenHiddenPower();
};

#endif // _WARRIOR_ACCESS_H_

このヘッダーファイルでは、BattleWarriorクラスを宣言しています。

ポイントは、publicとprivateが交互に出てくるところです。

public:
    int openPower;

private:
    int hiddenPower;

openPowerはpublicなので、main.cppからアクセスできます。
hiddenPowerはprivateなので、main.cppから直接アクセスできません。

また、メンバ関数も同じです。

public:
    void startTraining();

private:
    void awakenHiddenPower();

startTrainingはpublicなので、main.cppから呼び出せます。
awakenHiddenPowerはprivateなので、main.cppから直接呼び出せません。

アクセス指定子の有効範囲

アクセス指定子は、次のアクセス指定子が出てくるまで有効です。

たとえば、次のように書いた場合、

public:
    int openPower;

private:
    int hiddenPower;

openPowerはpublicです。
そのあとにprivateが出てくるので、hiddenPowerはprivateになります。

さらに、そのあとにpublicを書くと、そこから下はまたpublicになります。

public:
    void startTraining();

このように、アクセス指定子は「ここから先はpublic」「ここから先はprivate」という区切りを作ります。

記述その下に書かれたメンバの扱い
public:外からアクセスできる
private:クラス内からだけアクセスできる
protected:継承関係で使う範囲を含む

メンバ関数の定義

次に、warrior_access.cppにメンバ関数の中身を書きます。

プロジェクト/ファイル名: Chap3_03/warrior_access.cpp

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

using namespace std;

// 外から呼び出せる修行開始メソッド
void BattleWarrior::startTraining() {
    cout << "startTraining" << endl;

    // publicメンバ変数に値を代入する
    openPower = 120;

    // privateメンバ変数にも、クラス内からなら値を代入できる
    hiddenPower = 300;

    // privateメンバ関数も、クラス内からなら呼び出せる
    awakenHiddenPower();
}

// クラス内だけで呼び出す潜在能力解放メソッド
void BattleWarrior::awakenHiddenPower() {
    // publicメンバ変数を書き換える
    openPower = 200;

    // privateメンバ変数を書き換える
    hiddenPower = 500;

    // 現在の戦闘力を表示する
    cout << "openPower=" << openPower << ",hiddenPower=" << hiddenPower << endl;
}

このファイルでは、BattleWarriorクラスのメンバ関数を定義しています。

注目したいのは、startTrainingの中でprivateメンバにアクセスしているところです。

hiddenPower = 300;
awakenHiddenPower();

hiddenPowerはprivateメンバ変数です。
awakenHiddenPowerはprivateメンバ関数です。

main.cppからはアクセスできませんが、BattleWarriorクラスのメンバ関数であるstartTrainingの中からはアクセスできます。

つまり、privateは「完全に使えない」という意味ではありません。
クラスの外からは使えないが、クラスの中からは使えるという意味です。

図:クラス外からprivateへは入れないが、クラス内からは使える

この図が示していること

この図では、publicとprivateのアクセス範囲を表しています。

main.cppのようなクラス外からは、publicメンバにだけアクセスできます。
privateメンバには直接アクセスできません。

しかし、BattleWarriorクラスの中にあるstartTrainingからは、privateメンバであるhiddenPowerやawakenHiddenPowerを使えます。

privateは、外から隠すための指定であり、クラス内部の処理では利用できます。

main.cppからアクセスしてみる

最後に、main.cppを見てみましょう。

プロジェクト/ファイル名: Chap3_03/main.cpp

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

using namespace std;

int main(int argc, char** argv) {
    // BattleWarriorクラスのインスタンスを生成する
    BattleWarrior fighter;

    // publicメンバ変数にはクラス外からアクセスできる
    fighter.openPower = 100;

    // privateメンバ変数にはクラス外からアクセスできない
    // fighter.hiddenPower = 500;

    // publicメンバ関数はクラス外から呼び出せる
    fighter.startTraining();

    // privateメンバ関数はクラス外から呼び出せない
    // fighter.awakenHiddenPower();

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果は次のようになります。

startTraining
openPower=200,hiddenPower=500

このプログラムでは、main.cppからBattleWarriorクラスのインスタンスfighterを作っています。

BattleWarrior fighter;

そして、publicメンバ変数openPowerに値を代入しています。

fighter.openPower = 100;

openPowerはpublicなので、クラス外からアクセスできます。

次に、publicメンバ関数startTrainingを呼び出しています。

fighter.startTraining();

startTrainingもpublicなので、クラス外から呼び出せます。

privateメンバへ外からアクセスするとどうなるか

main.cppには、次の行をコメントとして書いています。

// fighter.hiddenPower = 500;

hiddenPowerはprivateメンバ変数です。
そのため、コメントを外して次のようにすると、ビルドエラーになります。

fighter.hiddenPower = 500;

同じように、次の行もコメントを外すとビルドエラーになります。

fighter.awakenHiddenPower();

awakenHiddenPowerはprivateメンバ関数なので、main.cppから直接呼び出せません。

main.cppからのアクセス結果
fighter.openPower = 100;OK
fighter.startTraining();OK
fighter.hiddenPower = 500;エラー
fighter.awakenHiddenPower();エラー

このように、publicとprivateによって、外から使えるメンバと使えないメンバを分けられます。

処理の流れを確認する

このプログラムの処理の流れを順番に見ていきましょう。

まず、main.cppでインスタンスを生成します。

BattleWarrior fighter;

これにより、BattleWarriorクラスからfighterという戦士オブジェクトが作られます。

次に、publicメンバ変数openPowerに100を代入します。

fighter.openPower = 100;

openPowerはpublicなので、main.cppからアクセスできます。

次に、publicメンバ関数startTrainingを呼び出します。

fighter.startTraining();

startTrainingの中では、次の処理が行われます。

cout << "startTraining" << endl;
openPower = 120;
hiddenPower = 300;
awakenHiddenPower();

ここで、hiddenPowerとawakenHiddenPowerはprivateですが、startTrainingは同じBattleWarriorクラスのメンバ関数です。
そのため、クラス内からprivateメンバへアクセスできます。

さらに、awakenHiddenPowerの中では、openPowerとhiddenPowerを2に近い形ではなく、より高い値へ書き換えています。

openPower = 200;
hiddenPower = 500;
cout << "openPower=" << openPower << ",hiddenPower=" << hiddenPower << endl;

最終的に、次のように表示されます。

openPower=200,hiddenPower=500

startTrainingからprivateメンバを呼び出せる理由

ここで大切なのは、privateメンバでも、同じクラスの中からなら使えるということです。

main.cppからは、次のように直接アクセスできません。

fighter.hiddenPower = 500;
fighter.awakenHiddenPower();

しかし、BattleWarrior::startTrainingの中からは、次のように使えます。

hiddenPower = 300;
awakenHiddenPower();

この違いを表にすると、次のようになります。

場所hiddenPowerawakenHiddenPower
main.cppアクセス不可呼び出し不可
BattleWarrior::startTrainingの中アクセス可能呼び出し可能
BattleWarrior::awakenHiddenPowerの中アクセス可能自分自身も含めてクラス内で扱える

ドラゴンボール風に言えば、外部の司令室からは、戦士の隠し戦闘力に直接触れません。
でも、戦士自身の修行処理の中では、その隠し戦闘力を呼び起こすことができます。

publicとprivateの使い分け

publicとprivateは、ただアクセスできるかどうかを変えるだけではありません。

プログラムを安全に、分かりやすくするために使い分けます。

置く場所向いているもの
public外から呼び出したい操作、外に見せてもよい情報
private外から直接変更させたくないデータ、内部だけで使う処理

たとえば、戦士オブジェクトで考えると、外部から修行開始を指示するstartTrainingはpublicでよいでしょう。

一方、hiddenPowerのような内部の重要な値はprivateにして、外から直接書き換えられないようにします。

こうすることで、クラスの外側から不正な値を入れられることを防ぎやすくなります。

カプセル化の入口

publicとprivateは、カプセル化という考え方につながります。

カプセル化とは、データと処理をクラスの中にまとめ、必要なものだけを外に公開し、大切な内部情報は隠す考え方です。

ドラゴンボール風にたとえるなら、戦士の体の中にある潜在能力や奥義の発動条件を、外部から勝手に触らせないようにするイメージです。

外部からは、startTrainingのような安全な入口だけを使います。
その内部で、hiddenPowerを調整したり、awakenHiddenPowerを呼び出したりします。

カプセル化の考え方BattleWarriorクラスでの例
外に公開する操作startTraining
内部に隠すデータhiddenPower
内部に隠す処理awakenHiddenPower
外から見えるデータopenPower

このように、publicとprivateを使い分けることで、クラスの内部を安全に守りながら、必要な操作だけを外に公開できます。

図:カプセル化は秘密の力を安全に守る仕組み

この図が示していること

この図では、カプセル化の考え方を表しています。

publicメンバは、外から使える入口です。
privateメンバは、クラスの内部に守られたデータや処理です。

外部からはprivateへ直接アクセスできません。
しかし、publicメンバ関数を通して、必要な処理だけを安全に実行できます。

アクセス指定子で押さえたいポイント

publicとprivateを理解すると、クラスの設計がかなり見えやすくなります。

特に押さえておきたいのは、次のポイントです。

ポイント内容
publicクラス外からアクセスできる
privateクラス外からアクセスできない
privateメンバ同じクラスのメンバ関数内からは使える
publicメンバ関数外部からクラスを操作する入口になる
カプセル化必要なものだけ公開し、内部を隠す考え方

BattleWarriorクラスでは、openPowerとstartTrainingがpublicです。
そのため、main.cppからアクセスできます。

一方、hiddenPowerとawakenHiddenPowerはprivateです。
そのため、main.cppからは直接アクセスできません。

しかし、startTrainingの中からはhiddenPowerやawakenHiddenPowerを使えます。

このように、publicとprivateを使うことで、クラスの外側と内側の役割を分けられます。

C++のオブジェクト指向では、外から何でも自由に触れるようにするのではなく、必要な入口だけを公開し、内部の大切な情報は守ることが大切です。

publicは外へ開く扉。
privateは内側に守る秘密の部屋。

この感覚を持っておくと、クラス設計がぐっと分かりやすくなります。