C++入門|静的メンバの利用

静的メンバ関数は、修行場全体の共通モニター。個別戦士の力には触れず、クラス全体で共有する情報だけを安全に扱おう。

C++のクラスでは、インスタンスを作ってから使うインスタンスメンバと、インスタンスを作らなくても使える静的メンバがあります。

前回は、静的メンバ変数を使うことで、クラス全体で共有する値を管理できることを学びました。

たとえば、修行場にいる戦士の人数を数える s_count のような値は、特定の1人の戦士だけが持つものではありません。
修行場全体で共有する情報です。

一方、各戦士が持つIDや個別の気の値は、それぞれのインスタンスごとに違います。
これはインスタンスメンバとして扱います。

今回のテーマは、静的メンバ関数とインスタンスメンバ関数の中から、どのメンバを使えるのかです。

ここは少し混乱しやすいところですが、ドラゴンボール風に考えると分かりやすくなります。

静的メンバ関数は、修行場の上空にある共通スカウターモニターのようなものです。
修行場全体の共有データは見られますが、「どの戦士の個別メーターを見るのか」は分かりません。

一方、インスタンスメンバ関数は、実際の戦士1人に結びついた操作です。
その戦士自身の個別データも見られますし、修行場全体の共有データも見られます。

関数の種類使えるものドラゴンボール風のイメージ
静的メンバ関数静的メンバ変数、静的メンバ関数修行場全体の共通モニター
インスタンスメンバ関数静的メンバ、インスタンスメンバの両方個別戦士の操作パネル

つまり、静的メンバ関数は、インスタンスがなくても呼び出せるため、特定のインスタンスに属する m_a のような値を直接使えません。
逆に、インスタンスメンバ関数は、すでに特定のインスタンスに結びついているため、個別データも共有データも扱えます。

図:静的メンバ関数とインスタンスメンバ関数の見える範囲

この図が示していること

この図では、静的メンバ関数とインスタンスメンバ関数が利用できる範囲の違いを表しています。

静的メンバ関数は、クラス全体に属する関数です。
そのため、クラス全体で共有される静的メンバ変数にはアクセスできます。

しかし、特定のインスタンスに属するインスタンスメンバ変数にはアクセスできません。
どの戦士の個別データなのかを判断できないからです。

静的メンバ関数から使えるもの

静的メンバ関数は、インスタンスを生成しなくても呼び出せます。

KiMonitor::showArenaStatus();

このように、クラス名::関数名() の形で呼び出せます。

しかし、インスタンスを必要としないということは、逆に言えば、特定のインスタンスに結びついていないということでもあります。

そのため、静的メンバ関数の中では、インスタンスメンバ変数を直接使えません。

たとえば、次のようなイメージです。

// 静的メンバ関数の中では直接使えない
m_personalPower = 300;

m_personalPower は、各戦士が個別に持つ気の値です。

1人目の戦士のm_personalPowerなのか、2人目の戦士のm_personalPowerなのか、静的メンバ関数からは判断できません。

そのため、静的メンバ関数から直接使うことはできません。

インスタンスメンバ関数から使えるもの

インスタンスメンバ関数は、特定のインスタンスを通して呼び出します。

monitor->checkPersonalPower();

このように呼び出した場合、monitor という特定のインスタンスに結びついています。

そのため、インスタンスメンバ関数の中では、次の両方を扱えます。

使えるもの理由
インスタンスメンバ変数どのインスタンスの値か分かるため
静的メンバ変数クラス全体で共有されているため
インスタンスメンバ関数同じインスタンスの処理として呼べるため
静的メンバ関数クラス全体の処理として呼べるため

つまり、インスタンスメンバ関数は、個別戦士のパネルを操作しながら、修行場全体の共通モニターも確認できる立場です。

使えるメンバの関係

静的メンバ関数とインスタンスメンバ関数から使えるメンバを整理すると、次のようになります。

種別静的メンバ変数静的メンバ関数インスタンスメンバ変数インスタンスメンバ関数
静的メンバ関数××
インスタンスメンバ関数

この表のポイントは、静的メンバ関数の行です。

静的メンバ関数は、静的メンバ変数や静的メンバ関数は使えます。
しかし、インスタンスメンバ変数やインスタンスメンバ関数は直接使えません。

なぜなら、静的メンバ関数は、インスタンスがなくても呼び出せるからです。

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

今回は、KiMonitorクラスを作ります。

KiMonitorは、近未来の修行場に置かれた気の測定モニターを表すクラスです。

このクラスには、次の2種類のメンバを用意します。

メンバ種類内容
m_personalPowerインスタンスメンバ変数個別モニターが測定した戦士の気
s_arenaPower静的メンバ変数修行場全体で共有する気の基準値
checkPersonalPowerインスタンスメンバ関数個別の気と共有の気を表示する
showArenaStatus静的メンバ関数修行場全体の共有値だけを表示する

このサンプルでは、インスタンスメンバ関数からは m_personalPower と s_arenaPower の両方にアクセスできます。
一方、静的メンバ関数からは s_arenaPower にだけアクセスします。

KiMonitorクラスの宣言

プロジェクト/ファイル名: Chap4_06/ki_monitor.h

#ifndef _KI_MONITOR_H_
#define _KI_MONITOR_H_

class KiMonitor {
public:
    // インスタンスメンバ関数
    void checkPersonalPower();

    // 静的メンバ関数
    static void showArenaStatus();

private:
    // インスタンスメンバ変数
    int m_personalPower;

    // 静的メンバ変数
    static int s_arenaPower;
};

#endif // _KI_MONITOR_H_

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

次の2つは、インスタンスメンバです。

void checkPersonalPower();
int m_personalPower;

checkPersonalPower は、インスタンスを生成してから呼び出す関数です。
m_personalPower は、インスタンスごとに持つ個別の値です。

次の2つは、静的メンバです。

static void showArenaStatus();
static int s_arenaPower;

showArenaStatus は、インスタンスを生成しなくても呼び出せる静的メンバ関数です。
s_arenaPower は、クラス全体で共有される静的メンバ変数です。

図:KiMonitorクラスの中にある2種類のメンバ

この図が示していること

この図では、KiMonitorクラスの中に、インスタンスメンバと静的メンバが共存している様子を表しています。

m_personalPower はインスタンスごとの個別データです。
s_arenaPower はクラス全体で共有するデータです。

checkPersonalPower はインスタンスメンバ関数なので、個別データにも共有データにもアクセスできます。
showArenaStatus は静的メンバ関数なので、共有データだけを扱います。

静的メンバ変数の定義と関数の中身

次に、ki_monitor.cpp を見ていきます。

プロジェクト/ファイル名: Chap4_06/ki_monitor.cpp

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

using namespace std;

// 静的メンバ変数の初期化
int KiMonitor::s_arenaPower = 0;

// インスタンスメンバ関数
void KiMonitor::checkPersonalPower() {
    cout << "=== checkPersonalPower ===" << endl;

    // インスタンスメンバ変数への代入
    m_personalPower = 1200;

    // 静的メンバ変数への代入
    s_arenaPower = 3000;

    // インスタンスメンバ変数の表示
    cout << "個別の気=" << m_personalPower << endl;

    // 静的メンバ変数の表示
    cout << "修行場全体の気=" << s_arenaPower << endl;
}

// 静的メンバ関数
void KiMonitor::showArenaStatus() {
    cout << "=== showArenaStatus ===" << endl;

    // インスタンスメンバ変数への代入はできない
    // m_personalPower = 5000;

    // 静的メンバ変数への代入
    s_arenaPower = 4000;

    // インスタンスメンバ変数の表示はできない
    // cout << "個別の気=" << m_personalPower << endl;

    // 静的メンバ変数の表示
    cout << "修行場全体の気=" << s_arenaPower << endl;
}

このファイルの最初では、静的メンバ変数 s_arenaPower を定義し、0で初期化しています。

int KiMonitor::s_arenaPower = 0;

静的メンバ変数は、ヘッダーファイルで宣言し、cppファイルで定義する必要があります。

ファイル書く内容役割
ki_monitor.hstatic int s_arenaPower;静的メンバ変数の宣言
ki_monitor.cppint KiMonitor::s_arenaPower = 0;静的メンバ変数の定義と初期化

インスタンスメンバ関数では両方使える

checkPersonalPower は、インスタンスメンバ関数です。

void KiMonitor::checkPersonalPower() {

この関数は、インスタンスを通して呼び出します。

monitor->checkPersonalPower();

そのため、この関数の中では、インスタンスメンバ変数 m_personalPower を使えます。

m_personalPower = 1200;

さらに、静的メンバ変数 s_arenaPower も使えます。

s_arenaPower = 3000;

つまり、インスタンスメンバ関数は、個別の情報と共有の情報の両方を扱えます。

ドラゴンボール風にたとえるなら、個別戦士のスカウターを操作しながら、修行場全体の気の総量モニターも確認できる状態です。

静的メンバ関数ではインスタンスメンバを直接使えない

showArenaStatus は、静的メンバ関数です。

void KiMonitor::showArenaStatus() {

この関数は、次のようにクラス名から呼び出せます。

KiMonitor::showArenaStatus();

インスタンスがなくても呼び出せるため、特定のインスタンスに属する m_personalPower を直接使うことはできません。

そのため、次の行はコメントにしています。

// m_personalPower = 5000;

もしコメントを外すと、コンパイルエラーになります。

m_personalPower = 5000;

なぜなら、m_personalPower はインスタンスごとの値だからです。

もし KiMonitor のインスタンスが2つあった場合、静的メンバ関数の中で m_personalPower と書いたとき、どちらのm_personalPowerを指すのか分かりません。

KiMonitor* monitor1 = new KiMonitor();
KiMonitor* monitor2 = new KiMonitor();

この状態で、静的メンバ関数の中から m_personalPower を直接使おうとしても、monitor1の値なのか、monitor2の値なのか判断できません。

そのため、静的メンバ関数からインスタンスメンバ変数へ直接アクセスできないのです。

main.cppで呼び出し方を確認する

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

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

using namespace std;

int main(int argc, char** argv) {
    // インスタンスを生成する
    KiMonitor* monitor = new KiMonitor();

    // インスタンスメンバ関数を呼び出す
    monitor->checkPersonalPower();

    // インスタンスメンバ関数をクラス名から呼び出すことはできない
    // KiMonitor::checkPersonalPower();

    // 静的メンバ関数をインスタンスから呼び出す書き方
    // 動作はするが、静的メンバであることが分かりにくい
    monitor->showArenaStatus();

    // 静的メンバ関数をクラス名から呼び出す書き方
    // こちらの書き方を推奨する
    KiMonitor::showArenaStatus();

    // インスタンスを消去する
    delete monitor;

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

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

=== checkPersonalPower ===
個別の気=1200
修行場全体の気=3000
=== showArenaStatus ===
修行場全体の気=4000
=== showArenaStatus ===
修行場全体の気=4000

この実行結果では、checkPersonalPower で個別の気と修行場全体の気が表示されています。

一方、showArenaStatus では、修行場全体の気だけが表示されています。

インスタンスメンバ関数の呼び出し

インスタンスメンバ関数 checkPersonalPower は、インスタンスを通して呼び出します。

monitor->checkPersonalPower();

monitor は KiMonitor のインスタンスを指すポインタです。

そのため、monitor->checkPersonalPower(); と書くことで、monitor が指しているインスタンスの checkPersonalPower 関数を呼び出せます。

一方、次のようには呼び出せません。

KiMonitor::checkPersonalPower();

checkPersonalPower はインスタンスメンバ関数なので、インスタンスが必要です。

クラス名だけでは、どのインスタンスの m_personalPower を使えばよいのか分かりません。

静的メンバ関数の呼び出し

静的メンバ関数 showArenaStatus は、クラス名を使って呼び出せます。

KiMonitor::showArenaStatus();

この書き方が、静的メンバ関数を呼び出すときの基本です。

サンプルでは、次のようにインスタンスからも呼び出しています。

monitor->showArenaStatus();

この書き方でも動作します。

しかし、あまりおすすめではありません。

なぜなら、monitor->showArenaStatus(); と書くと、まるでshowArenaStatusがmonitorというインスタンス専用の関数であるかのように見えるからです。

showArenaStatusは静的メンバ関数です。
つまり、インスタンスに属しているのではなく、KiMonitorクラス全体に属している関数です。

そのため、次のように書くほうが分かりやすいです。

KiMonitor::showArenaStatus();
呼び出し方動作推奨
monitor->showArenaStatus();呼び出せるあまり推奨しない
KiMonitor::showArenaStatus();呼び出せる推奨

図:静的メンバ関数はクラス名から呼び出す

この図が示していること

この図では、静的メンバ関数の呼び出し方を比較しています。

monitor->showArenaStatus(); でも呼び出せますが、インスタンスメンバ関数のように見えてしまいます。
そのため、静的メンバ関数は KiMonitor::showArenaStatus(); のように、クラス名から呼び出すほうが分かりやすいです。

静的メンバ関数からインスタンスメンバへアクセスできない理由

静的メンバ関数は、インスタンスを生成しなくても呼び出せます。

KiMonitor::showArenaStatus();

この時点で、KiMonitor のインスタンスが存在しているとは限りません。

もし、静的メンバ関数の中で m_personalPower を使えてしまうと、次の疑問が生まれます。

m_personalPower = 5000;

この m_personalPower は、どのインスタンスの値なのでしょうか。

monitor1でしょうか。
monitor2でしょうか。
それとも、まだインスタンスが存在しないのでしょうか。

このように、対象が決められないため、静的メンバ関数からインスタンスメンバへ直接アクセスすることはできません。

ドラゴンボール風に言えば、修行場の中央モニターから「個別戦士の気を5000にする」と言っても、どの戦士を指すのか分からない状態です。

インスタンスメンバ関数から静的メンバへアクセスできる理由

一方、インスタンスメンバ関数から静的メンバへアクセスすることはできます。

void KiMonitor::checkPersonalPower() {
    m_personalPower = 1200;
    s_arenaPower = 3000;
}

checkPersonalPower は、特定のインスタンスを通して呼び出されます。

monitor->checkPersonalPower();

この場合、m_personalPower は monitor が指しているインスタンスの値です。

そして、s_arenaPower はクラス全体で共有される値です。

そのため、個別データも共有データも、どちらも問題なく使えます。

コメントを外すとエラーになる部分

今回のサンプルでは、静的メンバ関数の中に次のコメントがあります。

// m_personalPower = 5000;

このコメントを外すと、コンパイルエラーになります。

m_personalPower = 5000;

m_personalPower はインスタンスメンバ変数なので、静的メンバ関数の中から直接使えないからです。

同じように、main.cppの次のコメントを外してもエラーになります。

// KiMonitor::checkPersonalPower();

checkPersonalPower はインスタンスメンバ関数なので、クラス名から直接呼び出すことはできません。

この2つのエラーは、静的メンバとインスタンスメンバの違いを理解するうえで、とても大切です。

エラーになる書き方理由
静的メンバ関数内で m_personalPower を使うどのインスタンスの値か分からない
KiMonitor::checkPersonalPower();インスタンスメンバ関数なのでインスタンスが必要

静的メンバとインスタンスメンバの使い分け

静的メンバとインスタンスメンバは、役割に応じて使い分けます。

使いたいもの向いているメンバ
クラス全体で共有する値静的メンバ変数
クラス全体に関する処理静的メンバ関数
各インスタンスごとの値インスタンスメンバ変数
各インスタンスごとの処理インスタンスメンバ関数

今回の例でいえば、修行場全体の気の基準値は s_arenaPower です。
これはクラス全体で共有するため、静的メンバ変数にしています。

個別モニターが測定した気は m_personalPower です。
これはインスタンスごとの値なので、インスタンスメンバ変数にしています。

このように、「全体で共有するのか」「個別に持つのか」を考えると、staticを付けるべきか判断しやすくなります。

静的メンバを使うときの大切な感覚

静的メンバ関数を使うときは、次のポイントを意識しましょう。

ポイント内容
静的メンバ関数はインスタンスなしで呼べるKiMonitor::showArenaStatus();
静的メンバ関数からインスタンスメンバへ直接アクセスできないどのインスタンスか分からないため
インスタンスメンバ関数から静的メンバへアクセスできる個別インスタンスも共有情報も扱えるため
静的メンバ関数はクラス名から呼び出すクラス全体の関数だと分かりやすい
staticを付ける対象を考える全体で共有するならstatic、個別に持つなら通常メンバ

静的メンバは、クラス全体で共有する情報や処理を扱うための仕組みです。

インスタンスメンバは、個々のインスタンスが別々に持つ情報や処理を扱うための仕組みです。

ドラゴンボール風に言えば、s_arenaPower は修行場全体の共通モニターに表示される値です。
m_personalPower は、それぞれの戦士ごとの個別スカウターに表示される値です。

共通モニターは、特定の戦士を選ばない限り、個別スカウターの値を勝手に操作できません。
この感覚が、静的メンバ関数からインスタンスメンバへ直接アクセスできない理由につながります。

静的メンバを使うときは、クラス全体のものなのか、インスタンスごとのものなのかを意識すると、使い分けがかなり分かりやすくなります。