static

静的メンバ変数

クラスはインスタンスを生成して使用するのが基本です。
インスタンスはいくつでも作成でき、各インスタンスが持つメンバ変数の値はそれぞれ独立しています。

これに対して、すべてのインスタンスで共通の値となるメンバ変数を作ることができます。
これを静的メンバ変数と言います。
静的メンバ変数はstaticキーワードで作成します。


#include <iostream>

class TestClass
{
    int num;

    //静的メンバ変数
    static int sNum;

public:
    TestClass(int n = 0) :num(n) {}

    int get() { return num; }

    int getStatic() { return sNum; }
    void setStatic(int n) { sNum = n; }
};

//静的メンバ変数の定義
int TestClass::sNum = 0;

int main()
{
    TestClass tc1(10);
    TestClass tc2(20);

    tc1.setStatic(50);

    std::cout << tc1.get() << std::endl; //10
    std::cout << tc2.get() << std::endl; //20

    std::cout << tc1.getStatic() << std::endl; //50
    std::cout << tc2.getStatic() << std::endl; //50

    std::cin.get();
}

インスタンスtc1を通して静的メンバ変数sNumの値を書き換えると、インスタンスtc2の方も同じ値になっていることがわかります。

静的メンバ変数はインスタンスに依存せず、クラスに依存する変数となります。
例えば全てのインスタンスで共通して使用する文字列を格納したり、現在のインスタンスの総数を保存しておくなどの使い方が考えられます。
いくつインスタンスを生成しても静的メンバ変数はひとつしか作られないので、メモリの節約にもなります。

静的メンバ変数はクラス内で宣言しますが、実際の定義はクラス外で行います。
定義はデータ型 クラス名::静的メンバ名 = 値;という形で、スコープ解決演算子(::)を使用します。
(ここではstaticキーワードは付けません)

staticグローバル変数およびstaticローカル変数は、宣言をすると(初期化をしなくても)同時に定義が行われます。
staticメンバ変数は宣言(メンバ変数として使用する事を記述する)のみでは定義は行われないので、別で定義が必要です。

宣言のみで定義をしないオブジェクト(変数など)を作ることは可能ですが、それを使用すると実体が存在しないためリンクエラーになります。
(ただし、その名前だけを必要とする場合は定義が無くてもエラーにはなりません。例えばクラス型のポインタ変数の使用はクラス名が必要ですが、定義は必要としません。)

静的メンバ変数をpublic領域に置いている場合、クラス名::静的メンバ変数名という形でアクセスすることができます。
静的メンバ変数は特定のインスタンスに依存しないので、インスタンスを生成せずに直接アクセスすることができます。


#include <iostream>

class TestClass
{
public:
    //静的メンバ変数
    static int sNum;
};

//静的メンバ変数の定義
int TestClass::sNum = 0;

int main()
{
    TestClass tc1, tc2;
    tc1.sNum = 1;
    tc2.sNum = 2;

    std::cout << tc1.sNum << std::endl; //2
    std::cout << tc2.sNum << std::endl; //2
    std::cout << TestClass::sNum << std::endl; //2

    std::cin.get();
}

静的constメンバ変数

静的メンバ変数がconstで整数型または列挙型である場合は、クラス内で定数式(コンパイル時定数)による初期化を行うことができます。


class TestClass
{
    static const int scNum1 = 1;
    static const int scNum2;
};
//クラス内で初期化しない場合は従来通りの定義が必要
const int TestClass::scNum2 = 2;

この場合は外部での定義は必要ありませんが、アドレスを取る、参照をする場合は外部定義が必要になります。
ただし初期化は行いません。


class TestClass
{
public:
    static const int scNum = 1;
};
//ここでは初期化はしない
const int TestClass::scNum;

int main()
{
    const int* p = &TestClass::scNum;
}

C++の規則では外部定義が必要ですが、Visual C++では無くてもエラーにならないようです。

静的メンバ関数

クラスはインスタンスを生成してから使うのが基本ですが、インスタンスを生成せずに直接呼び出せるメンバ関数を作ることができます。
これを静的メンバ関数と言います。


#include <iostream>
#include <string>
#include <cstdio>
#include <ctime>

class MyDateTime
{
    static std::tm getLocalTime()
    {
        std::time_t t;
        std::time(&t);
        std::tm local;
        localtime_s(&local, &t);
        return local;
    }

public:
    enum class TimeUnit { year, month, day, hour, minute, second };

    static int getDateTimeUnit(TimeUnit tu)
    {
        std::tm local = getLocalTime();
        switch (tu)
        {
        case TimeUnit::year:
            return local.tm_year + 1900;
        case TimeUnit::month:
            return local.tm_mon + 1;
        case TimeUnit::day:
            return local.tm_mday;
        case TimeUnit::hour:
            return local.tm_hour;
        case TimeUnit::minute:
            return local.tm_min;
        case TimeUnit::second:
            return local.tm_sec;
        default:
            return 0;
        }
    }

    static std::string getDateTime()
    {
        tm local = getLocalTime();
        char tmp[20];
        std::snprintf(tmp, 20, "%04d/%02d/%02d %02d:%02d:%02d",
            local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
            local.tm_hour, local.tm_min, local.tm_sec);
        return tmp;
    }

    static const char* getGreeting()
    {
        int hour = getDateTimeUnit(TimeUnit::hour);
        if (hour >= 5 && hour < 11)
            return "おはよう。";
        if ((hour >= 18 && hour < 24)
            || (hour >= 0 && hour < 5))
            return "こんばんは。";
        return "こんにちは。";
    }
};

int main()
{
    std::cout << MyDateTime::getDateTimeUnit(MyDateTime::TimeUnit::year) << std::endl;
    std::cout << MyDateTime::getDateTime() << std::endl;
    std::cout << MyDateTime::getGreeting() << std::endl;
    std::cin.get();
}

MyDateTimeクラス内のメンバ関数はすべてstaticで定義しています。
静的メンバ関数は、クラスのインスタンスを生成せずに直接呼び出して使用することができます。
呼び出し方は静的メンバ変数の時と同じくクラス名::静的メンバ関数名()という形で行います。
(外部から呼び出す場合はpublicである必要があります)

staticメンバ関数からのアクセス制限

静的メンバ関数からは、静的でないメンバ変数/関数にアクセスすることはできません。
非静的メンバは、インスタンスが生成されて初めて実体が作られます。
静的メンバはインスタンスに依存しないため、アクセス可能な非静的メンバ(インスタンス)が存在しないかもしれません。
反対に、インスタンスが複数存在する場合はどのインスタンスのメンバにアクセスすべきかが明確でないためです。

非静的メンバ関数から静的メンバ変数/関数にアクセスする場合はこのような問題はないため、アクセス可能です。


class TestClass
{
public:
    static int sNum;
    int num;

    static void sPrint()
    {
        //OK
        std::cout << sNum << std::endl;

        //NG
        //std::cout << num << std::endl;
    }

    void print()
    {
        //OK
        std::cout << sNum << std::endl;

        //OK
        std::cout << num << std::endl;
    }
};
int TestClass::sNum = 0;