C++入門|カプセル化とアクセス関数

戦士の本当の力は、外から直接いじらせない。privateで守り、setとgetで安全に扱うのが、C++のカプセル化の基本です。

C++のクラスでは、メンバ変数とメンバ関数を1つのまとまりとして扱います。

ここまでの学習では、メンバ変数をpublicにして、main.cppから直接値を代入する書き方を使ってきました。

たとえば、戦士の戦闘力を表すメンバ変数がpublicであれば、次のように外から直接変更できます。

warrior.power = 500;

これは分かりやすい書き方ですが、実際のプログラムでは少し注意が必要です。

なぜなら、publicなメンバ変数は、クラスの外側から自由に変更できてしまうからです。
もし戦闘力にマイナスの値や、想定外の値を入れられると、プログラムの動きがおかしくなる可能性があります。

そこで使うのが、カプセル化です。

カプセル化とは、クラスの内部データをprivateで守り、必要な操作だけをpublicなメンバ関数として公開する考え方です。

ドラゴンボール風にたとえるなら、戦士の本当の戦闘力や潜在能力を、外部の司令室から直接書き換えられないように、修行カプセルの中に守っておくようなものです。

外からは、直接データに触るのではなく、決められた入口を通して操作します。

その入口になるのが、アクセス関数です。

用語意味ドラゴンボール風のイメージ
privateメンバ変数クラス外から直接触れないデータ戦士の内側に隠された戦闘力
publicメンバ関数クラス外から呼び出せる操作修行カプセルの操作パネル
セッターprivateな値を設定する関数戦闘力を安全に登録する入口
ゲッターprivateな値を取得する関数戦闘力を読み取るスカウター
カプセル化内部データを守り、必要な操作だけ公開する考え方秘密の力をカプセル内に守る仕組み

今回の記事では、privateなメンバ変数を直接触らず、setPowerとgetPowerというアクセス関数を使って値を設定・取得する方法を学びます。

図:カプセル化はprivateな力を守る修行カプセル

この図が示していること

この図では、privateなメンバ変数m_powerを直接外から触らせず、publicなsetPowerとgetPowerを通して扱う様子を表しています。

main.cppのようなクラス外からは、m_powerへ直接アクセスできません。
代わりに、setPowerで値を設定し、getPowerで値を取得します。

これが、カプセル化とアクセス関数の基本的な考え方です。

カプセル化とは何か

カプセル化とは、クラスの中にあるデータを外から直接触れないようにし、必要な操作だけを外に公開する考え方です。

C++では、privateを使うことで、メンバ変数をクラスの外から直接アクセスできないようにできます。

private:
    int m_power;

このように書くと、m_powerはクラスの外側から直接使えません。

たとえば、main.cppから次のように書くことはできません。

warrior.m_power = 500;

m_powerがprivateだからです。

ただし、同じクラスのメンバ関数の中からはm_powerを使えます。
そのため、値を設定する関数や、値を取得する関数をpublicに用意します。

public:
    void setPower(int power);
    int getPower();

このようにして、privateなメンバ変数へアクセスするための入口を作ります。

なぜカプセル化が必要なのか

カプセル化が必要な理由は、大きく分けて2つあります。

1つ目は、意図しない値の変更を防ぐためです。

もし戦闘力を表す変数がpublicであれば、main.cppからどこでも自由に値を変更できます。

warrior.power = -9999;

このような値が入ってしまうと、戦闘力として不自然です。
プログラムの計算結果もおかしくなる可能性があります。

privateにしておけば、外から直接書き換えられません。
そして、setPowerの中で必要に応じて値のチェックを行うこともできます。

2つ目は、処理をクラスの中にまとめられるためです。

戦闘力を設定するときのルールや、値を取得するときの処理を、あちこちに書くのではなく、KiWarriorクラスの中にまとめられます。

これにより、あとからプログラムを見た人も理解しやすくなります。
また、変更が必要になったときも、クラスの中の処理を直せばよいので、影響を小さくしやすくなります。

ドラゴンボール風に言えば、戦士の潜在能力を外部から勝手に変更させるのではなく、修行カプセルの操作パネルを通して安全に設定するイメージです。

アクセス関数とは何か

アクセス関数とは、privateなメンバ変数へ値を設定したり、値を取得したりするためのメンバ関数です。

代表的なものが、セッターとゲッターです。

種類役割
セッターprivateなメンバ変数に値を設定するsetPower(int power)
ゲッターprivateなメンバ変数の値を取得するgetPower()

セッターは、setから始まる名前にすることが多いです。
ゲッターは、getから始まる名前にすることが多いです。

たとえば、m_powerというメンバ変数なら、次のような名前にします。

メンバ変数セッターゲッター
m_powersetPowergetPower

このように名前をそろえると、どのメンバ変数を扱う関数なのかが分かりやすくなります。

カプセル化のサンプルプログラム

ここからは、カプセル化とアクセス関数を使ったサンプルプログラムを見ていきます。

今回作成するのは、KiWarriorクラスです。

KiWarriorクラスでは、戦士の戦闘力を表すm_powerをprivateにします。
そして、m_powerに値を設定するsetPower、m_powerの値を取得するgetPowerをpublicにします。

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

ファイル役割
ki_warrior.hKiWarriorクラスの宣言を書く
ki_warrior.cppsetPowerとgetPowerの中身を書く
main.cppKiWarriorインスタンスを使う

KiWarriorクラスの宣言

プロジェクト/ファイル名: Chap3_04/ki_warrior.h

#ifndef _KI_WARRIOR_H_
#define _KI_WARRIOR_H_

class KiWarrior {
public:
    // 戦闘力を設定するセッター
    void setPower(int power);

    // 戦闘力を取得するゲッター
    int getPower();

private:
    // 外から直接変更できない戦闘力
    int m_power;
};

#endif // _KI_WARRIOR_H_

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

ポイントは、m_powerがprivateに置かれていることです。

private:
    int m_power;

m_powerは、戦士の戦闘力を表すメンバ変数です。
privateなので、main.cppから直接アクセスできません。

その代わりに、publicなメンバ関数としてsetPowerとgetPowerを用意しています。

public:
    void setPower(int power);
    int getPower();

この2つがアクセス関数です。

メンバアクセス指定子役割
setPowerpublicm_powerに値を設定する
getPowerpublicm_powerの値を取得する
m_powerprivate戦闘力を保存する

main.cppからは、m_powerを直接触るのではなく、setPowerとgetPowerを使います。

図:セッターとゲッターでprivateな戦闘力を扱う

この図が示していること

この図では、privateなm_powerを、setPowerとgetPowerを通して扱う流れを表しています。

main.cppからm_powerへ直接アクセスすることはできません。
しかし、publicなsetPowerを呼び出せば値を設定できます。
また、publicなgetPowerを呼び出せば値を取得できます。

これにより、戦闘力という内部データを守りながら、必要な操作だけを外に公開できます。

setPowerとgetPowerの定義

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

プロジェクト/ファイル名: Chap3_04/ki_warrior.cpp

#include "ki_warrior.h"

// 戦闘力を設定する
void KiWarrior::setPower(int power) {
    m_power = power;
}

// 戦闘力を取得する
int KiWarrior::getPower() {
    return m_power;
}

ここでは、setPowerとgetPowerを定義しています。

setPowerは、引数powerで受け取った値を、privateなメンバ変数m_powerへ代入します。

void KiWarrior::setPower(int power) {
    m_power = power;
}

getPowerは、m_powerの値をreturnで返します。

int KiWarrior::getPower() {
    return m_power;
}

セッターの役割

セッターは、privateなメンバ変数に値を設定するための関数です。

今回のセッターはsetPowerです。

void KiWarrior::setPower(int power) {
    m_power = power;
}

引数powerには、外から設定したい戦闘力が渡されます。
その値を、クラス内部のm_powerに代入しています。

名前役割
powersetPowerに渡される引数
m_powerKiWarriorクラスが持つprivateなメンバ変数

ドラゴンボール風に言えば、setPowerは修行カプセルの登録ボタンです。
外から戦闘力を直接書き換えるのではなく、登録ボタンを通して戦闘力を設定します。

ゲッターの役割

ゲッターは、privateなメンバ変数の値を取得するための関数です。

今回のゲッターはgetPowerです。

int KiWarrior::getPower() {
    return m_power;
}

getPowerは、m_powerの値をreturnで返します。

main.cppからgetPowerを呼び出すと、m_powerの値を受け取れます。

ドラゴンボール風に言えば、getPowerはスカウターです。
戦士の内部にある戦闘力を、外から安全に読み取るための窓口になります。

main.cppからアクセス関数を使う

最後に、main.cppでKiWarriorクラスを使います。

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

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

using namespace std;

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

    // セッターを使って戦闘力を設定する
    warrior.setPower(500);

    // ゲッターを使って戦闘力を取得して表示する
    cout << warrior.getPower() << endl;

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

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

500

このプログラムでは、KiWarriorクラスからwarriorというインスタンスを作っています。

KiWarrior warrior;

次に、setPowerを使って戦闘力を設定しています。

warrior.setPower(500);

そして、getPowerを使って戦闘力を取得し、coutで表示しています。

cout << warrior.getPower() << endl;

ここで大切なのは、main.cppからm_powerへ直接アクセスしていないことです。

privateなメンバ変数には直接アクセスできない

m_powerはprivateなので、main.cppから次のようには書けません。

warrior.m_power = 500;

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

なぜなら、m_powerはKiWarriorクラスの内部でだけ扱えるメンバ変数だからです。

正しくは、setPowerを通して値を設定します。

warrior.setPower(500);

値を取得するときも、m_powerを直接読むのではなく、getPowerを使います。

warrior.getPower()
やりたいこと直接アクセスアクセス関数を使う
戦闘力を設定するwarrior.m_power = 500; は不可warrior.setPower(500);
戦闘力を取得するwarrior.m_power は不可warrior.getPower();

このように、privateなメンバ変数は外から直接触れないようにし、publicなアクセス関数を通して扱います。

カプセル化された処理の流れ

このプログラムの処理を順番に追うと、次のようになります。

手順処理内容
1KiWarrior warrior;インスタンスを生成する
2warrior.setPower(500);セッターで戦闘力を設定する
3m_power = power;クラス内部でm_powerに値が入る
4warrior.getPower();ゲッターで戦闘力を取得する
5return m_power;クラス内部の値を返す
6coutで表示500が表示される

main.cppから見えるのは、setPowerとgetPowerだけです。
m_powerの存在は、クラス内部に隠されています。

このように、内部のデータをprivateで守り、必要な入口だけをpublicで公開するのがカプセル化です。

図:main.cppから見えるのはsetPowerとgetPowerだけ

この図が示していること

この図では、main.cppからKiWarriorオブジェクトを使う流れを表しています。

main.cppから見えるのは、publicなsetPowerとgetPowerです。
privateなm_powerはカプセル内部に守られていて、外から直接触ることはできません。

setPowerを通してm_powerに値を設定し、getPowerを通してm_powerの値を取得します。

セッターだけ、ゲッターだけにすることもできる

アクセス関数は、必ずセッターとゲッターを両方用意しなければならないわけではありません。

たとえば、ゲッターだけを用意すれば、外から値を読むことはできるが、変更はできないメンバ変数になります。

用意する関数外からできることイメージ
セッターとゲッター設定も取得もできる読み書き可能
ゲッターだけ取得だけできる読み取り専用
セッターだけ設定だけできる書き込み専用

たとえば、戦士のIDのように、外から読み取りはできても変更させたくないものは、ゲッターだけにすることがあります。

一方で、内部処理にだけ使う値であれば、アクセス関数をまったく用意しないこともあります。

このように、アクセス関数をどう用意するかによって、外からできる操作を細かく制御できます。

m_を付けるメンバ変数名

今回のサンプルでは、メンバ変数名をm_powerにしています。

int m_power;

このm_は、この変数がメンバ変数であることを分かりやすくするための接頭辞です。

たとえば、setPowerでは、引数powerとメンバ変数m_powerが登場します。

void KiWarrior::setPower(int power) {
    m_power = power;
}

このとき、m_powerはメンバ変数、powerは引数だと見分けやすくなります。

名前意味
m_powerクラスが持つメンバ変数
powersetPowerに渡される引数

m_を付ける書き方は、単純化されたハンガリアン記法のような考え方として紹介されることがあります。

現在のC++では、プロジェクトやチームによって命名規則はさまざまですが、入門段階では、メンバ変数と引数を区別しやすいという利点があります。

セッターとゲッターの名前の付け方

セッターとゲッターの名前は、扱うメンバ変数が分かるように付けると読みやすくなります。

今回のメンバ変数はm_powerです。

そのため、m_を除いたpowerをもとにして、次のように名前を付けています。

メンバ変数セッターゲッター
m_powersetPowergetPower

このようにすると、setPowerはm_powerに値を設定する関数、getPowerはm_powerの値を取得する関数だとすぐに分かります。

ほかのメンバ変数でも、同じように名前を付けられます。

メンバ変数セッターゲッター
int m_scorevoid setScore(int score)int getScore()
double m_valuevoid setValue(double value)double getValue()
bool m_flagvoid setFlag(bool flag)bool getFlag()
string m_namevoid setName(string name)string getName()

名前を見ただけで、どのメンバ変数を扱う関数なのか分かるようにしておくと、プログラムが読みやすくなります。

カプセル化でプログラムが整理しやすくなる

カプセル化を使うと、クラスの外側と内側の役割を分けられます。

main.cppのようなクラスの外側では、setPowerやgetPowerを呼び出すだけです。
m_powerがどのように保存されているか、内部でどんな処理が行われているかを詳しく知る必要はありません。

一方、KiWarriorクラスの中では、m_powerをどのように扱うかをまとめて管理できます。

場所役割
main.cpppublicな操作を呼び出す
KiWarriorクラス内部privateなデータを管理する
setPower値を設定する入口
getPower値を取得する入口

このように役割を分けることで、プログラムの見通しがよくなります。

大きなプログラムでは、クラスごとに役割を分けて開発することが多くなります。
そのとき、内部データをprivateで守り、必要な関数だけをpublicにする設計はとても重要です。

ドラゴンボール風に言えば、戦士の潜在能力そのものはカプセル内に守り、外部の司令室には安全な操作パネルだけを見せるということです。

カプセル化とアクセス関数で押さえたいポイント

カプセル化とアクセス関数では、次のポイントを押さえておきましょう。

ポイント内容
メンバ変数はprivateにする外から直接変更されないようにする
値を設定する関数を用意するセッターを使う
値を取得する関数を用意するゲッターを使う
必要な操作だけpublicにする外から使える入口を限定する
m_でメンバ変数を区別する引数やローカル変数と見分けやすくする
名前をそろえるsetPower、getPowerのように分かりやすくする

C++では、メンバ変数を何でもpublicにして外から自由に触らせるより、privateで守り、必要な操作だけをpublicな関数として用意する設計がよく使われます。

privateは、戦士の内側にある秘密の力を守るための壁です。
セッターは、その力を安全に設定する入口です。
ゲッターは、その力を安全に読み取る入口です。

この3つの関係を理解すると、C++のカプセル化がかなり自然に見えてきます。