C++入門|静的メンバとインスタンスメンバ

戦士ごとのIDはインスタンスメンバ、修行場全体の人数は静的メンバ。C++のstaticを使えば、インスタンスを超えて共有されるクラス全体の情報を管理できます。

これまで学んできたメンバ変数やメンバ関数は、多くの場合、インスタンスを生成してから使うものでした。

たとえば、KiWarriorクラスから1人の戦士を作ると、その戦士は自分専用のIDや状態を持ちます。
このように、インスタンスごとに持つメンバをインスタンスメンバといいます。

一方で、すべての戦士に共通する情報もあります。

たとえば、修行場に今何人の戦士がいるか、という情報は、1人の戦士だけのものではありません。
戦士1人1人が個別に持つというより、KiWarriorクラス全体で共有したい情報です。

このようなときに使うのが、静的メンバです。

静的メンバは、インスタンスを生成しなくても利用できるメンバです。
クラスそのものに属する情報や処理を表したいときに使います。

種類属する場所ドラゴンボール風のイメージ
インスタンスメンバ各インスタンス各戦士が個別に持つIDや動作
静的メンバクラス全体修行場全体で共有する人数カウンタ

ドラゴンボール風にたとえるなら、インスタンスメンバは各戦士が持つ「自分だけの戦士ID」です。
静的メンバは、修行場全体で共有される「現在の参加戦士数」です。

戦士が1人出現すれば人数カウンタが1増えます。
戦士が修行場から退場すれば人数カウンタが1減ります。

このような、すべてのインスタンスに関係する共通情報を扱うとき、staticがとても便利です。

図:インスタンスメンバと静的メンバの違い

この図が示していること

この図では、インスタンスメンバと静的メンバの違いを表しています。

m_idは、それぞれの戦士インスタンスが個別に持つ値です。
1人目の戦士はm_idが1、2人目の戦士はm_idが2、3人目の戦士はm_idが3というように、インスタンスごとに値が分かれます。

一方、s_countはKiWarriorクラス全体で共有される値です。
修行場に存在する戦士の数を、すべてのインスタンスで共通して管理します。

静的メンバとは何か

静的メンバとは、クラスに属するメンバです。

通常のメンバ変数は、インスタンスを作るたびにそれぞれ別々に用意されます。
しかし、静的メンバ変数は、クラス全体で1つだけ存在します。

たとえば、KiWarriorクラスに次のような静的メンバ変数を用意します。

static int s_count;

このs_countは、修行場にいる戦士の数を数えるための変数です。

戦士インスタンスが1人生成されるたびに、s_countを1増やします。
戦士インスタンスが消去されるたびに、s_countを1減らします。

このようにすれば、現在の戦士数をクラス全体で管理できます。

メンバ種類役割
m_idインスタンスメンバ変数各戦士の個別ID
s_count静的メンバ変数修行場全体の戦士数
chargeKiインスタンスメンバ関数各戦士が気を高める
showCount静的メンバ関数現在の戦士数を表示する

staticを付けて宣言する

静的メンバを宣言するときは、先頭にstaticを付けます。

静的メンバ変数の宣言は、次のような形になります。

static 型 変数名;

今回のサンプルでは、次のように書きます。

static int s_count;

このs_countは、KiWarriorクラス全体で共有する人数カウンタです。

また、静的メンバ関数も、先頭にstaticを付けて宣言します。

static void showCount();

showCountは、現在の修行場の戦士数を表示する関数です。
静的メンバ関数なので、インスタンスを作らなくても呼び出せます。

KiWarrior::showCount();

このように、クラス名のあとに::を付けて呼び出します。

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

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

KiWarriorクラスは、修行場に出現する気戦士を表すクラスです。

戦士が生成されると、コンストラクタで修行場の人数が1増えます。
戦士が消去されると、デストラクタで修行場の人数が1減ります。

また、各戦士にはm_idというインスタンスメンバ変数を持たせます。
m_idは、その戦士が何番目に出現したかを表す番号です。

ファイル役割
ki_warrior.hKiWarriorクラスの宣言
ki_warrior.cpp静的メンバ変数、コンストラクタ、デストラクタ、関数の定義
main.cppnewとdeleteで戦士を生成・消去し、人数を確認する

KiWarriorクラスの宣言

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

#ifndef _KI_WARRIOR_H_
#define _KI_WARRIOR_H_

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

    // デストラクタ
    ~KiWarrior();

    // 現在の修行場の戦士数を表示する静的メンバ関数
    static void showCount();

    // 気を高めるインスタンスメンバ関数
    void chargeKi();

private:
    // 各戦士が個別に持つID
    int m_id;

    // 修行場全体で共有する戦士数
    static int s_count;
};

#endif // _KI_WARRIOR_H_

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

注目したいのは、次の2つです。

static void showCount();
static int s_count;

showCountは静的メンバ関数です。
s_countは静的メンバ変数です。

どちらもstaticが付いています。

一方、m_idとchargeKiにはstaticが付いていません。

int m_id;
void chargeKi();

そのため、m_idとchargeKiはインスタンスメンバです。

m_idは各戦士ごとの個別IDです。
chargeKiは各戦士が気を高める処理です。

静的メンバ変数はcppファイルで定義する

静的メンバ変数は、ヘッダーファイルで宣言しただけでは使える状態になりません。

ヘッダーファイルでは、次のように宣言しています。

static int s_count;

そして、cppファイルで次のように定義して初期値を設定します。

int KiWarrior::s_count = 0;

この書き方によって、KiWarriorクラスのs_countに0が設定されます。

場所書く内容役割
ki_warrior.hstatic int s_count;静的メンバ変数の宣言
ki_warrior.cppint KiWarrior::s_count = 0;静的メンバ変数の定義と初期化

s_countは、プログラム開始時点で0として用意されます。

まだKiWarriorインスタンスが1人も作られていなくても、s_countは存在しています。

ドラゴンボール風に言えば、修行場の人数カウンタは、戦士が出現する前から司令室に用意されているということです。

KiWarriorクラスの定義

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

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

using namespace std;

// 修行場の戦士数の初期値を0に設定する
int KiWarrior::s_count = 0;

// コンストラクタ
KiWarrior::KiWarrior() : m_id(0) {
    // 戦士の数を1人増やす
    s_count++;

    // 現在の人数を、この戦士のIDとして設定する
    m_id = s_count;

    cout << "気戦士:" << m_id << "出現" << endl;
}

// デストラクタ
KiWarrior::~KiWarrior() {
    cout << "気戦士:" << m_id << "退場" << endl;

    // 戦士の数を1人減らす
    s_count--;
}

// 現在の修行場の戦士数を表示する
void KiWarrior::showCount() {
    cout << "現在の修行場の戦士数は、" << s_count << "人です。" << endl;
}

// 気を高める
void KiWarrior::chargeKi() {
    cout << m_id << ":気を高める!" << endl;
}

このcppファイルでは、静的メンバ変数s_countの初期化と、各メンバ関数の定義を行っています。

最初に、静的メンバ変数s_countを定義しています。

int KiWarrior::s_count = 0;

これは、修行場にまだ戦士がいない状態を表しています。

コンストラクタで人数を増やす

コンストラクタは、KiWarriorインスタンスが生成されたときに自動的に呼び出されます。

KiWarrior::KiWarrior() : m_id(0) {
    s_count++;
    m_id = s_count;

    cout << "気戦士:" << m_id << "出現" << endl;
}

ここでは、まずs_countを1増やしています。

s_count++;

そのあと、m_idにs_countの値を代入しています。

m_id = s_count;

たとえば、1人目の戦士が出現すると、s_countは1になります。
その値をm_idに入れるので、1人目の戦士のm_idは1になります。

2人目が出現すると、s_countは2になり、m_idも2になります。

このように、s_countは修行場全体の人数を管理し、m_idは各戦士の個別IDとして使われます。

図:戦士が生成されるたびにs_countが増える

この図が示していること

この図では、KiWarriorインスタンスが生成されるたびに、静的メンバ変数s_countが増えていく様子を表しています。

s_countはクラス全体で共有される値です。
そのため、1人目、2人目、3人目の戦士が生成されるたびに、同じs_countが1、2、3と変化します。

一方、m_idは各戦士インスタンスが個別に持つ値です。
各戦士は、自分自身のm_idを持っています。

デストラクタで人数を減らす

デストラクタは、KiWarriorインスタンスが消去される直前に自動的に呼び出されます。

KiWarrior::~KiWarrior() {
    cout << "気戦士:" << m_id << "退場" << endl;

    // 戦士の数を1人減らす
    s_count--;
}

ここでは、退場する戦士のm_idを表示したあと、s_countを1減らしています。

s_count--;

newで生成したインスタンスをdeleteすると、デストラクタが呼ばれます。

delete w1;

このタイミングで、w1のデストラクタが呼び出され、修行場の人数が1人減ります。

ドラゴンボール風に言えば、戦士が修行場から退場すると、司令室の人数カウンタが1つ減るイメージです。

静的メンバ関数showCount

showCountは、現在の修行場の戦士数を表示する静的メンバ関数です。

void KiWarrior::showCount() {
    cout << "現在の修行場の戦士数は、" << s_count << "人です。" << endl;
}

showCountはstatic付きで宣言しているため、インスタンスを通さずに呼び出せます。

KiWarrior::showCount();

この書き方は、クラスに属する関数を呼び出していることを表しています。

通常のインスタンスメンバ関数なら、次のようにインスタンスを通して呼び出します。

w1->chargeKi();

しかし、showCountはクラス全体の人数を表示する処理なので、特定の戦士を通して呼び出す必要がありません。

関数種類呼び出し方
showCount静的メンバ関数KiWarrior::showCount();
chargeKiインスタンスメンバ関数w1->chargeKi();

静的メンバ関数は個別インスタンスに依存しない

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

そのため、特定の戦士だけが持つm_idのようなインスタンスメンバには、直接アクセスできません。

showCountの中で使っているのはs_countです。

cout << "現在の修行場の戦士数は、" << s_count << "人です。" << endl;

s_countは静的メンバ変数なので、showCountから扱えます。

一方、m_idは各インスタンスが持つ個別の値です。
静的メンバ関数は特定のインスタンスに属していないため、どの戦士のm_idなのか判断できません。

このため、静的メンバ関数では、基本的に静的メンバを扱うと考えると分かりやすいです。

インスタンスメンバ関数chargeKi

chargeKiは、各戦士が気を高めるインスタンスメンバ関数です。

void KiWarrior::chargeKi() {
    cout << m_id << ":気を高める!" << endl;
}

chargeKiの中では、m_idを使っています。

m_idはインスタンスメンバ変数なので、戦士ごとに値が違います。

たとえば、w1のm_idは1、w2のm_idは2、w3のm_idは3です。

w1->chargeKi();
w2->chargeKi();
w3->chargeKi();

それぞれ同じchargeKi関数を呼び出していますが、表示されるIDは異なります。

呼び出し使われるm_id表示例
w1->chargeKi();11:気を高める!
w2->chargeKi();22:気を高める!
w3->chargeKi();33:気を高める!

このように、インスタンスメンバ関数は、そのインスタンスが持つメンバ変数を使って処理します。

main.cppで戦士を生成して確認する

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

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

using namespace std;

int main(int argc, char** argv) {
    // 気戦士を指すポインタ変数を用意する
    KiWarrior* w1;
    KiWarrior* w2;
    KiWarrior* w3;

    // 1人目の気戦士を生成する
    w1 = new KiWarrior();
    w1->chargeKi();

    // 現在の修行場の戦士数を表示する
    KiWarrior::showCount();

    // 2人目、3人目の気戦士を生成する
    w2 = new KiWarrior();
    w3 = new KiWarrior();

    // 2人目、3人目の気戦士が気を高める
    w2->chargeKi();
    w3->chargeKi();

    // 現在の修行場の戦士数を表示する
    KiWarrior::showCount();

    // 1人目、2人目の気戦士を消去する
    delete w1;
    delete w2;

    // 現在の修行場の戦士数を表示する
    KiWarrior::showCount();

    // 3人目の気戦士を消去する
    delete w3;

    // 現在の修行場の戦士数を表示する
    KiWarrior::showCount();

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

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

気戦士:1出現
1:気を高める!
現在の修行場の戦士数は、1人です。
気戦士:2出現
気戦士:3出現
2:気を高める!
3:気を高める!
現在の修行場の戦士数は、3人です。
気戦士:1退場
気戦士:2退場
現在の修行場の戦士数は、1人です。
気戦士:3退場
現在の修行場の戦士数は、0人です。

この実行結果を見ると、s_countが戦士の生成と消去に合わせて変化していることが分かります。

プログラム開始時の状態

プログラム開始時、静的メンバ変数s_countは0で初期化されています。

int KiWarrior::s_count = 0;

この時点では、まだKiWarriorインスタンスは生成されていません。

しかし、s_countは静的メンバ変数なので、インスタンスが存在しなくても使える状態になっています。

状態s_count
プログラム開始直後0
戦士インスタンスなし0

ドラゴンボール風に言えば、修行場の人数カウンタだけが先に起動していて、まだ戦士は誰もいない状態です。

1人目の戦士を生成する

main.cppでは、次の行で1人目の戦士を生成しています。

w1 = new KiWarrior();

この処理によって、KiWarriorクラスのインスタンスが1つ生成されます。

newで生成されるため、コンストラクタが呼び出されます。

コンストラクタの中では、s_countを1増やし、その値をm_idに設定しています。

s_count++;
m_id = s_count;

そのため、1人目の戦士はm_idが1になります。

続いて、w1->chargeKi();を呼び出します。

w1->chargeKi();

w1のm_idは1なので、次のように表示されます。

1:気を高める!

そのあと、静的メンバ関数showCountを呼び出します。

KiWarrior::showCount();

s_countは1なので、次のように表示されます。

現在の修行場の戦士数は、1人です。

2人目、3人目の戦士を生成する

次に、2人目と3人目の戦士を生成します。

w2 = new KiWarrior();
w3 = new KiWarrior();

w2を生成すると、s_countが2になります。
そのため、w2のm_idは2になります。

w3を生成すると、s_countが3になります。
そのため、w3のm_idは3になります。

ポインタ変数生成後のm_idその時点のs_count
w111
w222
w333

そのあと、w2とw3のchargeKiを呼び出します。

w2->chargeKi();
w3->chargeKi();

それぞれ次のように表示されます。

2:気を高める!
3:気を高める!

この段階で、showCountを呼び出すと、s_countは3なので、現在の修行場の戦士数は3人と表示されます。

KiWarrior::showCount();

現在の修行場の戦士数は、3人です。

1人目と2人目の戦士を消去する

続いて、1人目と2人目の戦士を消去します。

delete w1;
delete w2;

delete w1が実行されると、w1のデストラクタが呼び出されます。

気戦士:1退場

このとき、s_countが1減ります。

続いてdelete w2が実行されると、w2のデストラクタが呼び出されます。

気戦士:2退場

さらにs_countが1減ります。

最初に3人いたので、2人退場すると、残りは1人になります。

この状態でshowCountを呼び出します。

KiWarrior::showCount();

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

現在の修行場の戦士数は、1人です。

残っているのはw3だけです。

最後の戦士を消去する

最後に、3人目の戦士を消去します。

delete w3;

このとき、w3のデストラクタが呼び出されます。

気戦士:3退場

そして、s_countが1減り、0になります。

最後にshowCountを呼び出します。

KiWarrior::showCount();

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

現在の修行場の戦士数は、0人です。

この時点で、KiWarriorインスタンスは1つも残っていません。

それでも、showCountは静的メンバ関数なので呼び出せます。

ここが、静的メンバ関数の大きな特徴です。

図:生成と消去でs_countが変化する流れ

この図が示していること

この図では、KiWarriorインスタンスの生成と消去によって、静的メンバ変数s_countが変化する流れを表しています。

戦士が生成されるたびにs_countは増えます。
戦士が消去されるたびにs_countは減ります。

s_countは特定の戦士だけの値ではなく、KiWarriorクラス全体で共有される値です。

静的メンバとインスタンスメンバの違いを整理する

静的メンバとインスタンスメンバの違いを表にすると、次のようになります。

項目静的メンバインスタンスメンバ
所属クラス全体各インスタンス
使うためのインスタンス不要必要
主な使い方共通情報、共通処理個別情報、個別動作
変数の例s_countm_id
関数の例showCountchargeKi
呼び出し方KiWarrior::showCount();w1->chargeKi();

静的メンバは、クラス全体で共有する情報を扱うときに便利です。
インスタンスメンバは、個々のオブジェクトが別々に持つ情報を扱うときに使います。

s_とm_の名前の違い

今回のサンプルでは、メンバ変数名に次のような接頭辞を付けています。

接頭辞意味
m_インスタンスメンバ変数m_id
s_静的メンバ変数s_count

m_idは、各戦士が個別に持つIDです。
そのため、m_を付けています。

s_countは、クラス全体で共有される静的メンバ変数です。
そのため、s_を付けています。

このように名前で区別しておくと、コードを読んだときに、その変数がインスタンスごとのものなのか、クラス全体で共有されるものなのか分かりやすくなります。

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

静的メンバでは、次のポイントをしっかり押さえておきましょう。

ポイント内容
staticを付ける静的メンバ変数や静的メンバ関数として宣言する
クラス全体で共有されるインスタンスごとではなく、クラスに1つだけ存在する
インスタンスなしでも使えるKiWarrior::showCount();のように呼び出せる
静的メンバ変数はcppで定義するint KiWarrior::s_count = 0; のように書く
インスタンスメンバとは役割が違うm_idは個別、s_countは共有
生成と消去の管理に使えるコンストラクタで増やし、デストラクタで減らす

静的メンバを使うと、インスタンスをまたいで共有したい情報をクラスに持たせることができます。

今回のKiWarriorクラスでは、s_countを使って、修行場にいる戦士の人数を管理しました。

各戦士のIDはm_idとして個別に持ちます。
修行場全体の人数はs_countとして共有します。

この違いを理解すると、静的メンバとインスタンスメンバの使い分けがかなり分かりやすくなります。

ドラゴンボール風に言えば、m_idは各戦士が持つ個別の戦士番号、s_countは司令室が管理する全体人数カウンタです。

個別の力と、全体の情報。
この2つを分けて考えることが、静的メンバを理解する大きなポイントです。