3.16 静的クラスメンバ  (2000.03.21 初版)

3.16.1 クラスの属性とオブジェクトの属性

 クラスの各オブジェクトはクラスのすべてのデータメンバの値をそれぞれ独自に持っ
ています。しかし、特定のデータをすべてのオブジェクトで共有したい場合があります。

 例えば、学校の学生の成績を管理するクラスを設計するとします。このクラスのデー
タメンバとして、学生の名前、番号、及び科目名と点数などが考えられます。各オブジ
ェクトは、それぞれ対応する学生の個人データをメンバとして持ちます。ぞれぞれのオ
ブジェクトのデータメンバは、他のオブジェクトのものとは無関係(独立)です。

 更に成績を集計するために、合計点や平均点といったデータメンバも必要になります
が、これらは前のデータメンバとは異なる性格を持っています。合計点や平均点などは
個々の学生に属するのではなく、学生全体に共通するものです。一人の学生の点数が変
更されれば合計点も変わり、合計点が変わればすべてのオブジェクトは影響を受けます
(順位が変わるなど)。合計点や平均点はすべての学生に共通するデータです。つまり
これらは、クラス(オブジェクト全体)に所属するが、各オブジェクトには所属しない
データメンバです。

 別の例をあげると、銀行の預金口座を表すクラスでは、口座番号、名義、預金額は個
々のオブジェクトに所属するデータメンバですが、預金者の利息を計算するために必要
な金利は、全体に属するデータメンバです。この様な例は、いくらでも考えることがで
きます。

 C言語では、全体にわたる情報を保持するのにグローバル変数を使いました。グロー
バル変数を使えば、どの関数からもアクセスできます。しかし、この様なプログラムは
関数のモジュール性(独立性)を損うという欠点があります。同様に、クラスでグロー
バル変数を使えば、カプセル化が損なわれます。
 C++ではすべてのオブジェクトが共有するメンバとして、静的クラスメンバを使いま
す。静的クラスメンバは、先頭にキーワードstaticを付けて宣言します。

3.16.2 静的データメンバと静的メンバ関数

 クラス定義で、先頭にキーワードstaticを付けて宣言されたデータメンバを静的デ
ータメンバと呼びます。これは、個々のオブジェクトに属するのではなくて、すべての
オブジェクトで共有される、クラスのデータメンバです。静的データメンバは、オブジ
ェクトとは独立に存在し、その記憶場所は1つだけです。つまりオブジェクトの生成に
よって作成されるのではなく、プログラムの実行開始時に作成されます。静的データメ
ンバはオブジェクトが1つも存在していないときにも存在します。そのため、静的デー
タメンバは、コンストラクタによってではなく、ファイルスコープにおいて唯1回だけ
初期化しなければなりません。

 また静的データメンバは、クラススコープの中にあります。メンバ関数の中からアク
セスできます。オブジェクトが存在しないときに、非公開の静的データメンバにアクセ
スするには特殊なメンバ関数が必要です。これが、静的メンバ関数です。

 静的メンバ関数は、先頭にキーワードstaticを付けて宣言します。静的メンバ関数
も個々のオブジェクトに属しない、オブジェクト全体に共通なクラスのメンバ関数です。
つまり、静的データメンバと静的メンバ関数はオブジェクトから独立して存在します。
従って、静的メンバ関数はthisポインタを持ちません。


 次のプログラムは、静的データメンバと静的メンバ関数の使い方を示しています。こ
れは、上の例で述べた成績管理クラスや預金口座クラスでの静的クラスメンバの機能を
単純化したものです。クラスXは、整数型のデータメンバdataを持ちます。また各オブ
ジェクトの内部データdataの合計値を表すのが静的データメンバtotalです。公開の静
的メンバ関数get_total()はtotalの値を返します。
// lst03_44.cpp
// 静的データメンバと静的メンバ関数
#include <iostream.h>
#include <time.h>

class X{

public:
    explicit X(int i = 0) : data(i) { total +=  data; }
    ~X() { total -= data; }
    static int get_total() { return total; } // 静的メンバ関数

private:
    int data;
    static int total; // 静的データメンバ(ただの宣言)
};

int X::total = 0; // 静的データメンバの初期化

int main()
{
    // オブジェクトはまだ存在しない
    cout << "総数:" << X::get_total() << endl;

    X a(1000);
    X b(500);
    X c(37);

    cout << "総数:" << X::get_total() << endl;
    cout << "総数:" << a.get_total() << endl;

    return 0;
}
[実行結果]      ソースプログラム ( lst03_44.cpp )
総数:0
総数:1537
総数:1537
静的データメンバtotalは、クラスの外のファイルスコープで0に初期化されます。

int X::total = 0; // 定義による初期化

この文は、代入ではなく初期化であることに注意しましょう。定義による初期化を行っ
ています。クラスの外でメンバを初期化するためにスコープ解決演算子(::)を使いま
す。静的データメンバをファイルスコープで初期化しなければならない理由は、main()
関数が呼ばれる前に作成されるからです。静的データメンバは、グローバル変数の使用
を避けるために導入されたことを思い出しましょう。

 また、ここにはキーワードstaticが無いことにも注意しましょう。同様に、静的メ
ンバ関数get_total()をクラスの外部で定義するには、

int X::get_total()
{
    return total;
}

というように、キーワードstaticは付けません。staticはクラス内での宣言にだけ
付けます。これと対照的なのが、constメンバ関数です。関数をconstにするには、
両方で必要です。これはconstが関数の型の一部だからでした。


 静的公開メンバ関数get_total()を呼び出すには、クラス名にスコープ解決演算子を
使って呼び出します。

     X::get_total()

実行結果の1行目では、まだクラスのオブジェクトは存在しません。この関数は、初期
値の0(ゼロ)を返します。

 オブジェクトが作成されるとき、コンストラクタはそのオブジェクトの内部データの
値を合計値に加算します。この様にオブジェクトが作られるたびに、静的データメンバ
は変更され、すべてのオブジェクトで共有されます。

 静的メンバ関数は、クラススコープの中にあるので、個々のオブジェクトからも呼び
出すことができます。

     a.get_total()
     b.get_total()
     c.get_total()

どのオブジェクトを使っても結果は同じです。この点が、フレンド関数とは違います。
フレンド関数は、非公開メンバにアクセスできますが、クラススコープを持たない外部
関数です。


 既に述べたことですが静的メンバ関数は、個々のオブジェクトとは独立したものです
から、thisポインタを持ちません。そのため、静的メンバ関数からアクセスできるの
は静的クラスメンバだけです。非静的クラスメンバにアクセスするには、静的メンバ関
数の引数にオブジェクトへのポインタまたは参照を渡して、そのオブジェクトを介して
アクセスするしかありません。このことから、静的メンバ関数は次のような性質を持つ
ことが分かります。
 非静的クラスメンバにアクセスしないメンバ関数はstatic宣言することができます。
また、静的メンバ関数をconst宣言することはできません。コンパイルエラーになりま
す。constthisポインタを定数化するキーワードだからです。


[演習問題 3.15] 上のプログラムで、静的メンバ関数get_total()をconst宣言する         と、コンパイルエラーになることを確かめなさい。また、どのような         エラーメッセージが表示されるか確認しなさい。

3.16.3 静的データメンバの実験

 静的データメンバがいつ初期化されるかを見るための、簡単な実験プログラムを考え
ましょう。行列クラスの効率について検討したときに、C標準ライブラリ<time.h> 内
のclock() 関数を使ってメンバ関数の実行時間を求めました。これを、クラスで表現し
てみます。
 次に示すプログラムは、2つのイベント間の時間を計測するクラス timerです。この
クラスは、メンバ関数start()で計測を開始し、メンバ関数stop()で終了させて、その
時間間隔をメンバ関数show()で表示するストップウオッチ機能を持ちます。
 このときに、静的データメンバを使ってプログラムが実行を開始する時刻も記録でき
るようにします(クラス機能としては余分なことですが)。
// lst03_45.cpp
// タイマークラス
#include <iostream.h>
#include <time.h>

class timer{

public:
    timer() : begin(0), end(0) {  }         // 空のコンストラクタ
    ~timer() { }                            // 空のデストラクタ
    void start() { begin = clock(); }
    void stop() { end = clock(); }
    void show() const {
        cout << endl << "2つのイベント間の実行時間: "
             << ( end - begin ) /  (float) CLOCKS_PER_SEC
             << " 秒" << endl;

        cout << "プログラム開始の時刻: "
             << start_time /  (float) CLOCKS_PER_SEC
             << " 秒" << endl;

        cout << "(プログラム開始からの時間: "
             << ( end - start_time ) /  (float) CLOCKS_PER_SEC 
             << " 秒)" << endl;
    }

private:
    static clock_t start_time; // 静的データメンバ
    clock_t begin, end;
};

clock_t timer::start_time = clock(); // 静的データメンバの初期化

int main()
{
    timer t;
    char c;
    // 時間稼ぎ
    cout << "1文字入力して改行してください: ";
    cin >> c;

    t.start(); // 計測開始
    // 時間稼ぎ
    cout << "1文字入力して改行してください: ";
    cin >> c;

    t.stop(); // 計測終了
    t.show(); // 表示

    return 0;
}
[実行結果]      ソースプログラム ( lst03_45.cpp )
1文字入力して改行してください: 1
1文字入力して改行してください: 2

2つのイベント間の時間: 2.03 秒
プログラム開始の時刻: 0 秒
プログラム開始からの時間: 3.75 秒
実行結果はGCC-2.95/Mingw32(Minimalist GNU-Win32)によるものです。静的データメ
ンバstart_timeは、main()が呼ばれる前に初期化されます。関数clock()は、プログラ
ムが実行を開始してからそれまでに使用したプロセッサ時間を返すので、start_time
にはそれが初期化された時刻が格納されます。この値が0秒だとプログラム開始時とい
うことになります(コンパイラの仕様によって違いがあります)。


 ここでの静的データメンバは、初期化後に変更する必要性はないので、const宣言し
てもよいでしょう。
class timer{
public:

      // ・・・・・

private:
    static const clock_t start_time; // 静的データメンバ
      // ・・・・・
};

const clock_t timer::start_time = clock(); // 静的データメンバの初期化
この様に、静的データメンバはconst宣言することもできます。これに対して、普通の
非静的データメンバは注意が必要です。復習になりますが、非静的データメンバを定数
化する場合、メンバ初期設定子を使ってコンストラクタを定義しなければなりません。

3.16.4 クラスの管理

 静的クラスメンバは、オブジェクト全体を管理するために使うことができます。代表
的な例は、静的データメンバを使ってオブジェクトの個数を追跡することです。次のプ
ログラムを見てください。
// lst03_46.cpp
// オブジェクトの個数を追跡する
#include <iostream.h>
#include <time.h>

class X{

public:
    explicit X(int i = 0) : data(i) { ++count; }
    ~X() { --count; }
    static int get_count() { return count; } // 静的メンバ関数

private:
    int data;
    static int count; // 静的データメンバ
};

int X::count = 0; // 静的データメンバの初期化

int main()
{
    cout << "オブジェクトを1個生成" << endl;
    X a(5);
    cout << "--- オブジェクトの個数:" << X::get_count() << endl;

    {
        cout << "\nブロック内でオブジェクトの配列(次元数10)を生成" << endl;
        X b[10];
        cout << "--- オブジェクトの個数:" << X::get_count() << endl;
    }
    cout << "\nブロックを出た後:" << endl;
    cout << "--- オブジェクトの個数:" << X::get_count() << endl;

    cout << "\nオブジェクトを1個生成" << endl;
    X c(7);

    cout << "--- オブジェクトの個数:" << X::get_count() << endl;


    return 0;
}
[実行結果]      ソースプログラム ( lst03_46.cpp )
オブジェクトを1個生成
--- オブジェクトの個数:1

ブロック内でオブジェクトの配列(次元数10)を生成
--- オブジェクトの個数:11

ブロックを出た後:
--- オブジェクトの個数:1

オブジェクトを1個生成
--- オブジェクトの個数:2
静的データメンバ countは、現在存在するオブジェクトの個数をカウントします。コン
ストラクタが呼び出される度にカウンタを1つ増やし、デストラクタが呼ばれる度に1
つ減らします。ただし、このカウンタは「一時オブジェクト」も数えるので、プログラ
ムによっては注意が要ります。

| 目次  | 前のページ  | 次のページ  | ページの先頭  |

Copyright(c) 2000 Yamada, K