C++入門|protectedメンバ

protectedは、外部には見せないけれど、子クラスには受け継がせる秘伝の気。C++の継承では、親クラスの大切な情報を安全に子クラスへ渡せます。

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

ここまでよく使ってきたアクセス指定子は、主に次の2つです。

アクセス指定子使える場所イメージ
publicクラス外からも使えるだれでも押せる修行場の操作ボタン
private自分のクラス内だけで使える戦士本人だけが扱える体内の気の核

今回学ぶのは、3つ目のアクセス指定子であるprotectedです。

protectedは、privateとpublicの中間のような性質を持ちます。
クラス外からは使えませんが、継承した子クラスからは使えます。

ドラゴンボール風にたとえるなら、protectedは流派を受け継いだ弟子だけが使える秘伝の気です。

一般の外部プログラムからは触れません。
しかし、親クラスを継承した子クラスなら、その秘伝の情報や処理を使えます。

今回の記事では、親クラスとしてBaseFighterクラスを作ります。
そこから、子クラスとしてKameFighterクラスKamiFighterクラスを作ります。

クラス親クラス役割ファイル
BaseFighterなし基本戦士の共通情報を持つbase_fighter.h、base_fighter.cpp
KameFighterBaseFighter亀仙流の戦士kame_fighter.h、kame_fighter.cpp
KamiFighterBaseFighter神流の戦士kami_fighter.h、kami_fighter.cpp

BaseFighterには、戦士名と必殺技名を表すメンバ変数を用意します。
この2つをprotectedにすることで、子クラスからは使えるけれど、main.cppのようなクラス外からは直接使えない状態にします。

また、戦士名と必殺技名を設定するinit関数もprotectedにします。

これにより、KameFighterやKamiFighterのコンストラクタからはinitを呼び出せます。
しかし、main.cppからpKame->init(...)のように直接呼び出すことはできません。

図:protectedは子クラスから使える秘伝の領域

この図が示していること

この図では、protectedメンバがどこから使えるのかを表しています。

BaseFighterのprotectedメンバであるm_name、m_technique、initは、KameFighterやKamiFighterのような子クラスからは使えます。

一方、main.cppのようなクラス外からは使えません。
つまり、protectedは子クラスには開かれているけれど、外部には閉じている領域です。

protectedメンバの基本イメージ

protectedメンバは、継承と一緒に使うと意味が分かりやすくなります。

親クラスの中に、子クラスへ受け継がせたい情報や処理があるとします。
しかし、それをクラス外から自由に触らせたくはありません。

このようなときにprotectedを使います。

アクセス指定子クラス外から子クラスから自分のクラス内から
public
protected×
private××

protectedは、子クラスから見ると使えるメンバです。
しかし、クラス外から見るとprivateのように使えません。

ドラゴンボール風に言えば、protectedは「流派を継いだ戦士には教えるが、一般の見学者には触らせない秘伝書」です。

今回作成するサンプルプログラム

今回のサンプルでは、BaseFighterを親クラスにします。

BaseFighterには、戦士名と必殺技名を持たせます。
さらに、戦士名と必殺技名を設定するinit関数を用意します。

このinit関数はprotectedにします。

そのため、KameFighterやKamiFighterのコンストラクタからは呼び出せますが、main.cppから直接呼び出すことはできません。

メンバアクセス指定子役割
m_nameprotected戦士名
m_techniqueprotected必殺技名
getNamepublic戦士名を取得する
shoutTechniquepublic必殺技名を表示する
initprotected戦士名と必殺技名を設定する

BaseFighterのpublicメンバであるgetNameとshoutTechniqueは、main.cppから呼び出せます。
しかし、protectedメンバであるinitは、main.cppから呼び出せません。

BaseFighterクラスの宣言

まずは、親クラスとなるBaseFighterを宣言します。

プロジェクト/ファイル名: Chap5_02/base_fighter.h

#ifndef _BASE_FIGHTER_H_
#define _BASE_FIGHTER_H_

#include <string>

class BaseFighter {
protected:
    // 戦士名
    std::string m_name;

    // 必殺技名
    std::string m_technique;

public:
    // コンストラクタ
    BaseFighter();

    // デストラクタ
    virtual ~BaseFighter();

    // 戦士名を取得する
    std::string getName();

    // 必殺技を叫ぶ
    void shoutTechnique();

protected:
    // 戦士情報を初期化する
    void init(std::string name, std::string technique);
};

#endif // _BASE_FIGHTER_H_

このBaseFighterクラスでは、m_nameとm_techniqueをprotectedにしています。

protected:
    std::string m_name;
    std::string m_technique;

この2つは、BaseFighterクラス内から使えます。
さらに、BaseFighterを継承した子クラスからも使えます。

一方、main.cppのようなクラス外からは直接使えません。

また、init関数もprotectedにしています。

protected:
    void init(std::string name, std::string technique);

initは、戦士名と必殺技名を設定するための関数です。
子クラスのコンストラクタから呼び出して、各流派の戦士情報を設定します。

BaseFighterクラスの定義

次に、BaseFighterクラスのメンバ関数を定義します。

プロジェクト/ファイル名: Chap5_02/base_fighter.cpp

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

using namespace std;

// コンストラクタ
BaseFighter::BaseFighter() {
}

// デストラクタ
BaseFighter::~BaseFighter() {
}

// 戦士名を取得する
string BaseFighter::getName() {
    return m_name;
}

// 必殺技を叫ぶ
void BaseFighter::shoutTechnique() {
    cout << m_technique << "!" << endl;
}

// 戦士情報を初期化する
void BaseFighter::init(string name, string technique) {
    m_name = name;
    m_technique = technique;
}

getNameはpublicメンバ関数です。

string BaseFighter::getName() {
    return m_name;
}

そのため、main.cppから呼び出せます。

cout << pKame->getName() << endl;

shoutTechniqueもpublicメンバ関数です。

void BaseFighter::shoutTechnique() {
    cout << m_technique << "!" << endl;
}

これもmain.cppから呼び出せます。

一方、initはprotectedメンバ関数です。

void BaseFighter::init(string name, string technique) {
    m_name = name;
    m_technique = technique;
}

この関数は、戦士名と必殺技名を設定します。
ただし、protectedなので、main.cppから直接呼び出すことはできません。

子クラスであるKameFighterやKamiFighterの中から呼び出して使います。

KameFighterクラスの宣言

次に、BaseFighterを継承したKameFighterクラスを作ります。

プロジェクト/ファイル名: Chap5_02/kame_fighter.h

#ifndef _KAME_FIGHTER_H_
#define _KAME_FIGHTER_H_

#include "base_fighter.h"

class KameFighter : public BaseFighter {
public:
    // コンストラクタ
    KameFighter();

    // デストラクタ
    virtual ~KameFighter();
};

#endif // _KAME_FIGHTER_H_

ここで大切なのは、次の部分です。

class KameFighter : public BaseFighter

これは、KameFighterがBaseFighterをpublic継承しているという意味です。

KameFighterはBaseFighterの子クラスなので、BaseFighterのpublicメンバ関数を利用できます。
さらに、BaseFighterのprotectedメンバにも、KameFighterクラス内からアクセスできます。

KameFighterクラスの定義

KameFighterのコンストラクタでは、BaseFighterのprotectedメンバ関数initを呼び出します。

プロジェクト/ファイル名: Chap5_02/kame_fighter.cpp

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

using namespace std;

// コンストラクタ
KameFighter::KameFighter() {
    // 亀仙流戦士の名前と必殺技を設定する
    init("亀仙流の小戦士", "青白い気弾");
}

// デストラクタ
KameFighter::~KameFighter() {
    cout << m_name << "は " << m_technique << " を放って修行場をあとにする!" << endl;
}

コンストラクタでは、次のようにinitを呼び出しています。

init("亀仙流の小戦士", "青白い気弾");

initはBaseFighterでprotectedとして定義されています。

KameFighterはBaseFighterを継承しているため、KameFighterの中からinitを呼び出せます。

また、デストラクタでは、m_nameとm_techniqueを使っています。

cout << m_name << "は " << m_technique << " を放って修行場をあとにする!" << endl;

m_nameとm_techniqueもBaseFighterのprotectedメンバです。

子クラスであるKameFighterの中からは使えるため、このように表示できます。

図:子クラスのコンストラクタからprotectedなinitを呼び出す

この図が示していること

この図では、KameFighterのコンストラクタから、BaseFighterのprotectedメンバ関数initを呼び出している流れを表しています。

initはクラス外からは呼び出せません。
しかし、KameFighterはBaseFighterの子クラスなので、クラス内からinitを呼び出せます。

その結果、m_nameとm_techniqueに値を設定できます。

KamiFighterクラスの宣言

続いて、もう1つの子クラスとしてKamiFighterを作ります。

プロジェクト/ファイル名: Chap5_02/kami_fighter.h

#ifndef _KAMI_FIGHTER_H_
#define _KAMI_FIGHTER_H_

#include "base_fighter.h"

class KamiFighter : public BaseFighter {
public:
    // コンストラクタ
    KamiFighter();

    // デストラクタ
    virtual ~KamiFighter();
};

#endif // _KAMI_FIGHTER_H_

KamiFighterも、BaseFighterをpublic継承しています。

class KamiFighter : public BaseFighter

そのため、KamiFighterもBaseFighterのpublicメンバ関数を利用できます。
さらに、KamiFighterクラス内からはBaseFighterのprotectedメンバを使えます。

KamiFighterクラスの定義

KamiFighterのコンストラクタでも、protectedなinitを呼び出します。

プロジェクト/ファイル名: Chap5_02/kami_fighter.cpp

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

using namespace std;

// コンストラクタ
KamiFighter::KamiFighter() {
    // 神流戦士の名前と必殺技を設定する
    init("神流の癒し手", "癒しの光");
}

// デストラクタ
KamiFighter::~KamiFighter() {
    cout << m_name << "は " << m_technique << " をまとって静かに退場する!" << endl;
}

KamiFighterのコンストラクタでも、initを呼び出しています。

init("神流の癒し手", "癒しの光");

このように、親クラスBaseFighterでprotectedにしたinitを、複数の子クラスから使えます。

KameFighterでは亀仙流の情報を設定し、KamiFighterでは神流の情報を設定しています。

これにより、初期化の仕組みは親クラスにまとめつつ、子クラスごとに異なる値を設定できます。

main.cppで動作を確認する

最後に、main.cppを作ります。

main.cppでは、KameFighterとKamiFighterのインスタンスを生成し、getNameとshoutTechniqueを呼び出します。

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

#include <iostream>
#include "kame_fighter.h"
#include "kami_fighter.h"

using namespace std;

int main(int argc, char** argv) {
    // 亀仙流戦士クラスのインスタンスを生成する
    KameFighter* pKame = new KameFighter();

    // protectedメンバ関数なので、クラス外からは呼び出せない
    // pKame->init("未熟な戦士", "小さな気弾");

    // 戦士名を表示する
    cout << pKame->getName() << endl;

    // 必殺技を表示する
    pKame->shoutTechnique();

    // 亀仙流戦士のインスタンスを消去する
    delete pKame;

    cout << endl;

    // 神流戦士クラスのインスタンスを生成する
    KamiFighter* pKami = new KamiFighter();

    // 戦士名を表示する
    cout << pKami->getName() << endl;

    // 必殺技を表示する
    pKami->shoutTechnique();

    // 神流戦士のインスタンスを消去する
    delete pKami;

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

実行結果

亀仙流の小戦士
青白い気弾!
亀仙流の小戦士は 青白い気弾 を放って修行場をあとにする!

神流の癒し手
癒しの光!
神流の癒し手は 癒しの光 をまとって静かに退場する!

この実行結果では、KameFighterとKamiFighterが、それぞれ異なる名前と必殺技を持っていることが分かります。

名前と必殺技は、子クラスのコンストラクタでinitを呼び出して設定しています。

BaseFighterクラスの役割

BaseFighterクラスは、すべての戦士に共通する情報と処理を持つ親クラスです。

メンバアクセス指定子内容
m_nameprotected戦士名
m_techniqueprotected必殺技名
getNamepublic戦士名を取得する
shoutTechniquepublic必殺技を表示する
initprotected戦士名と必殺技を設定する

m_nameとm_techniqueは、子クラスであるKameFighterやKamiFighterから使えます。

ただし、main.cppから次のように直接使うことはできません。

// cout << pKame->m_name << endl;

m_nameはprotectedなので、クラス外から直接アクセスできないためです。

外部から戦士名を知りたい場合は、publicなgetNameを使います。

cout << pKame->getName() << endl;

このように、外部には安全な入口だけを公開し、内部の大切な情報はprotectedで守ります。

KameFighterとKamiFighterの役割

KameFighterとKamiFighterは、BaseFighterを継承した子クラスです。

それぞれのコンストラクタで、protectedなinitを呼び出しています。

KameFighterの場合

KameFighter::KameFighter() {
    init("亀仙流の小戦士", "青白い気弾");
}

KamiFighterの場合

KamiFighter::KamiFighter() {
    init("神流の癒し手", "癒しの光");
}

initはBaseFighterのprotectedメンバ関数です。

子クラスであるKameFighterとKamiFighterからは呼び出せるため、それぞれの戦士情報を初期化できます。

しかし、main.cppからは呼び出せません。

// pKame->init("未熟な戦士", "小さな気弾");

このコメントを外すと、protectedメンバにクラス外からアクセスしようとするため、コンパイルエラーになります。

protectedメンバは外部から呼び出せない

main.cppには、次のようなコメントがあります。

// pKame->init("未熟な戦士", "小さな気弾");

このコメントを外して、次のように書くとエラーになります。

pKame->init("未熟な戦士", "小さな気弾");

なぜなら、initはprotectedメンバ関数だからです。

protectedメンバは、子クラスからは使えます。
しかし、main.cppのようなクラス外からは使えません。

これは、外部から勝手に戦士名や必殺技を書き換えられないようにするためです。

ドラゴンボール風に言えば、initは戦士の流派と必殺技を設定する秘伝の儀式です。
その儀式は、BaseFighterを受け継いだ正式な子クラスだけが使えます。
修行場の外部端末から勝手に呼び出すことはできません。

publicメンバは外部から呼び出せる

一方で、getNameとshoutTechniqueはpublicです。

std::string getName();
void shoutTechnique();

そのため、main.cppから呼び出せます。

cout << pKame->getName() << endl;
pKame->shoutTechnique();

publicメンバは、クラス外へ公開された機能です。

戦士名を知りたい、必殺技を表示したい、という外部からの操作は、publicメンバ関数を通して行います。

このように、外部に使わせたい処理はpublicにします。
子クラスだけに使わせたい処理はprotectedにします。
完全に自分のクラス内だけで使いたい情報はprivateにします。

図:public、protected、privateのアクセス範囲

この図が示していること

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

publicは、クラス外からも子クラスからも使えます。

protectedは、子クラスからは使えますが、クラス外からは使えません。

privateは、自分のクラス内からしか使えません。

protectedは、継承を使うときに、親クラスから子クラスへ安全に情報や処理を渡すために役立ちます。

protectedメンバが便利な場面

protectedは、子クラスにだけ使わせたいメンバがあるときに便利です。

今回のinitは、戦士名と必殺技名を設定する大切な関数です。

これをpublicにしてしまうと、main.cppから自由に呼び出せてしまいます。

pKame->init("別の名前", "別の技");

このように外部から呼び出せると、子クラスのコンストラクタで決めた流派設定をあとから勝手に変更できてしまいます。

それは少し危険です。

一方、privateにしてしまうと、子クラスからも呼び出せません。

// privateだと、KameFighterの中からinitを呼び出せない

そこでprotectedにします。

initのアクセス指定結果
public外部からも呼び出せてしまう
private子クラスから呼び出せない
protected子クラスから呼べるが、外部からは呼べない

今回のinitには、protectedがぴったりです。

protectedメンバ変数を子クラスで使う

KameFighterのデストラクタでは、BaseFighterのprotectedメンバ変数を使っています。

KameFighter::~KameFighter() {
    cout << m_name << "は " << m_technique << " を放って修行場をあとにする!" << endl;
}

m_nameとm_techniqueはBaseFighterで宣言されています。

protected:
    std::string m_name;
    std::string m_technique;

KameFighterはBaseFighterを継承しているため、KameFighterのメンバ関数内からm_nameとm_techniqueを使えます。

KamiFighterでも同じです。

KamiFighter::~KamiFighter() {
    cout << m_name << "は " << m_technique << " をまとって静かに退場する!" << endl;
}

このように、protectedメンバ変数は、子クラスで共通のデータとして使えます。

ただし、便利だからといって何でもprotectedにするのはよくありません。
外部から隠したいだけで、子クラスにも触らせる必要がない情報はprivateのままにするほうが安全です。

getNameとshoutTechniqueは親クラスの機能をそのまま使える

main.cppでは、KameFighterとKamiFighterの両方でgetNameとshoutTechniqueを呼び出しています。

cout << pKame->getName() << endl;
pKame->shoutTechnique();

cout << pKami->getName() << endl;
pKami->shoutTechnique();

getNameとshoutTechniqueは、BaseFighterで定義されたpublicメンバ関数です。

KameFighterとKamiFighterはBaseFighterを継承しているため、これらの関数をそのまま使えます。

メンバ関数BaseFighterKameFighterKamiFighter
getName
shoutTechnique
initクラス内から○クラス内から○
initをmain.cppから呼ぶ×××

この表から分かるように、protectedなinitは子クラス内からは使えます。
しかし、main.cppからは呼び出せません。

protectedメンバで押さえたいポイント

protectedメンバでは、次のポイントを押さえておきましょう。

ポイント内容
protectedは子クラスから使える継承したクラス内からアクセスできる
protectedは外部から使えないmain.cppから直接呼び出せない
privateより子クラス向け子クラスへ受け継がせたい処理に向いている
publicより安全外部に自由に触らせたくない処理を隠せる
initのような初期化処理に向いている子クラスのコンストラクタからだけ呼ばせたい場合に便利

今回のBaseFighterでは、m_name、m_technique、initをprotectedにしました。

これにより、KameFighterやKamiFighterは、親クラスの情報を使って自分自身の初期化や終了メッセージを作れます。

一方、main.cppからはprotectedメンバにアクセスできません。

この仕組みによって、親クラスの共通機能を子クラスへ受け継がせながら、外部からの勝手な操作を防げます。

ドラゴンボール風に言えば、protectedは流派を継いだ戦士だけが扱える秘伝の気です。
外部の見学者には触らせず、子クラスの中だけで安全に使わせる。
それがprotectedメンバの大きな役割です。