C++編【言語解説】 第15章 static の使い方

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

static (静的)

この章では、C++ における staticキーワードの使い道をまとめてみます。

知っての通り、staticキーワードはC言語にも存在しており、 C言語編では、ローカル変数(第22章)、 グローバル変数(第23章)、 関数(第23章)に付ける使い方を説明しています。 これらの意味合いは、基本的に C++ でも同様ですが、オブジェクトが絡むと注意事項が増えるので、改めて確認していきます。

静的ローカル変数

関数の中で、staticキーワードを付けて宣言される変数は、静的ローカル変数になります。 このような変数は、関数を抜けても削除されることはありません(C言語編第22章参照)。 従って、

void func(int num)
{
	static MyClass c;
	
}  // オブジェクト c は削除されないので、MyClassクラスのデストラクタは呼び出されない

このように書いた場合、func関数を抜け出しても MyClassクラスのデストラクタは呼び出されません。 静的ローカル変数として作られたオブジェクトは、一度作られればずっとメモリ上に存在することになるため、 プログラム終了時までデストラクタが呼び出されることがありません。

また、プログラム終了時には、他にも様々なオブジェクトが削除される可能性があるため、 削除順に依存してしまうような処理を、デストラクタに書かないように注意する必要があります


C言語と比べると、メモリ領域に確保されるタイミングが異なることに注意して下さい。 C++ では、静的ローカル変数の定義を行っている箇所が初めて実行されるときに、メモリ確保が行われます。 勿論、コンストラクタの呼び出しもこのタイミングで行われます。
このため、次のようなコードはC言語ではエラーになりますが、C++ では問題ありません。

void func(int num)
{
	static int s = num;  // C言語ではプログラム開始時点で確保されるが、そのタイミングでは num の値が分からない
}

静的メンバ変数

メンバ変数にも staticキーワードを付けることができ、これは静的メンバ変数と呼ばれます。

通常のメンバ変数が、オブジェクト1つごとに別個に存在しているのに対して、 静的メンバ変数は、クラスに対して1つだけしか存在しません。 そのため、オブジェクトを生成せずに使用することができます。 また、オブジェクトを複数生成したとしたら、静的メンバ変数はそれぞれで共有されます。

このような特徴を持つため、静的メンバ変数は、オブジェクトそれぞれが必要とする共通情報を管理することに利用できます。 例えば、以下の例では、現在インスタンス化されているオブジェクトの総数を管理しています。

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
	MyClass();
	~MyClass();

private:
	static int msObjectCount;  // 静的メンバ変数の宣言
};

#endif
// MyClass.cpp

#include "MyClass.h"

// 静的メンバ変数の実体
int MyClass::msObjectCount = 0;

MyClass::MyClass()
{
	++msObjectCount;
}

MyClass::~MyClass()
{
	--msObjectCount;
}

静的メンバ変数は、クラス定義内に記述しただけでは定義したことになりません。 このサンプルプログラムで、MyClass.cpp の側に書いているように、実体となる定義を別のところに書く必要があります。
初期値を与えたければ、実体の方に記述します。 初期値を明示的に与えなかった場合には、デフォルトコンストラクタ(第13章)が使用されます。

また、静的メンバ変数の名前の先頭に「ms」を付けましたが、「s」があることで「static」であることを示しています。 もちろん、こういった命名方法は強制されたものではありませんが、当サイトではこれで統一します。


クラスの外から、静的メンバ変数にアクセスするには、「クラス名::静的メンバ変数の名前」という形で、 スコープ解決演算子を使って記述します(もちろん、「公開」されている必要があります)。
クラス定義内や、メンバ関数の定義内などから、自クラスの静的メンバ変数へアクセスする場合は、 単に静的メンバ変数の名前だけを使って記述できます

ところで、静的メンバ変数のメモリ領域ですが、静的でないメンバ変数とは別のところに取られています。 先ほどのサンプルプログラムのように、静的メンバ変数の実体は、クラス定義の外側にあるのです。 オブジェクトの個数とは無関係に、プログラム全体で1つしか存在しないので、オブジェクトのサイズに影響を与えません。

静的メンバ関数

メンバ関数にも staticキーワードを付けることができ、これは静的メンバ関数と呼ばれます。

静的メンバ関数は、オブジェクトを生成せずに呼び出すことができます。 ですから、「公開」されていれば、事実上、通常の関数と同条件になりますが、 クラスに含まれていることによって、スコープを限定できるという点が特徴的です。

オブジェクトが生成されていなくても呼び出せるので、 静的メンバ関数から、静的でないメンバをアクセスすることはできません
一方、静的でないメンバ関数から、静的メンバ関数を呼び出すことには問題ありません。

「静的メンバ変数」の項で取り上げたサンプルプログラムに、静的メンバ関数を追加してみます。

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
	MyClass();
	~MyClass();
	
	static int GetObjectCount();

private:
	static int msObjectCount;  // 静的メンバ変数の宣言
};

#endif
// MyClass.cpp

#include "MyClass.h"

// 静的メンバ変数の実体
int MyClass::msObjectCount = 0;

MyClass::MyClass()
{
	++msObjectCount;
}

MyClass::~MyClass()
{
	--msObjectCount;
}

int MyClass::GetObjectCount()
{
	return msObjectCount;
}
// main.cpp
#include <iostream>
#include "MyClass.h"

int main()
{
	std::cout << MyClass::GetObjectCount() << std::endl;

	MyClass c1;
	MyClass* c2 = new MyClass();
	std::cout << MyClass::GetObjectCount() << std::endl;
	
	delete c2;
	std::cout << MyClass::GetObjectCount() << std::endl;
}

実行結果:

0
2
1

関数宣言時に、先頭に staticキーワードを付ける以外は、通常のメンバ関数と同じように宣言・定義すれば良いです。


なお、特に例示はしませんが、 静的メンバ関数をオーバーロードしたり、デフォルト引数を持ったり、インライン関数にしたりすることにも制限はありません。

テンプレート化することも可能です(第19章

静的クラス

メンバがすべて静的であるクラスを静的クラスと呼ぶことがあります。

静的クラスが特別な能力を持っている訳ではありませんが、 静的メンバはオブジェクトを生成せずに利用できるため、静的クラスはインスタンス化の必要性が無いという点が特徴的です。
そこで、もし静的クラスを作るのであれば、インスタンス化を禁止するように設計するのが親切でしょう。 C++ でインスタンス化を禁止するには、コンストラクタを「非公開」にします。

class MyClass {
private:
	MyClass();  // 唯一のコンストラクタが「非公開」なら、インスタンス化できない
};

なお、このコンストラクタが呼び出される可能性は無いので、実は実装を書く必要もありませんし、書くべきでもありません。 もし、次のように MyClassクラスをインスタンス化しようとしたとします。

int main()
{
	MyClass* c = new MyClass();
}

この場合、コンストラクタが「非公開」ですから、アクセスできずにコンパイルエラーになるので、問題ありません。 問題なのは、MyClassクラス自身のメンバ関数の中で、コンストラクタを使おうとしてしまった場合です。

void MyClass::func()
{
	MyClass* c = new MyClass();
}

この場合、自分のクラスが持っているメンバへのアクセスですから、「非公開」であっても関係ありません。 そのため、アクセスできないことを理由にコンパイルエラーになることはありません。 しかし、インスタンス化しないようにしたいという目的があるので、何とかエラーにしたいところです。
そこで、コンストラクタの実装をあえて書かないという手段を採ります。 すると、コンパイル自体は通ってしまいますが、リンクの段階で、関数の実装が見つからないという意味合いのリンクエラーが起こります。

C++11 の場合、コンストラクタを delete で消してしまう方が良いです(第13章)。 この方法ならば、上述のどちらの場合でもコンパイルエラーになります。


静的クラスの価値は、ある種の機能群を1つのスコープにまとめることにあります。 1つのヘッダファイルに機能群をまとめ、何かのスコープに限定されない形で表現することは可能ですし、 C言語であればそうするでしょうが、C++ ならば、静的クラスを用いて「クラス名::メンバ」のようにスコープを限定することができます。

例えば、ファイルをコピーしたり削除したりするような機能は、 それぞれ1つの関数で完結できるため、メンバ変数を持つ必要もありません。 そのため、静的でない通常のクラスで表現する意味があまりありません。 そこで、静的クラスを使って、次のように定義できます。

class FileSystem {
public:
	static void Copy(const char* src, const char* dest);
	static void Delete(const char* path);
	
private:
	FileSystem();
};

しかし、スコープを限定するという目的ならば、名前空間を使う方が素直であるとも言えます。 実際、C++ で静的クラスを表現する場合は、クラスではなく、名前空間を使うのも良い方法です。 これなら、コンストラクタを「非公開」にするという、ある種の小細工も不要です。

namespace FileSystem {
	void Copy(const char* src, const char* dest);
	void Delete(const char* path);
}

クラスで表現しても、名前空間で表現しても、それぞれの関数の使い方が変わらないことに注目しましょう。

FileSystem::Copy("test.txt", "test_copy.txt");
FileSystem::Delete("test.txt");

名前空間の方なら、using宣言、usingディレクティブによって、「FileSystem::」の部分を省略できますが、 本質的には違いはありません。

静的グローバル変数

ソースファイル中で、staticキーワードを付けて宣言される変数は、静的グローバル変数になります。 このような変数は、そのソースファイル内からしかアクセスできません(C言語編第23章参照)。

ただし、C++ の場合、staticキーワードを付けるよりも、無名名前空間を使うことが推奨されます第3章)。

staticキーワードは本来、記憶域期間が静的であることを示すものであって、スコープを表現するものでは無いという考え方から来ています。 スコープを表現するのなら、名前空間を使う方がストレートであると言えます。

static const double VERSION = 1.0.0;  // 非推奨

namespace {
	const double VERSION = 1.0.0;  // 推奨
}

静的グローバル変数は、プログラム開始時点で既にメモリに確保された状態となり、開始直後にコンストラクタが呼び出されます。 デストラクタは、プログラムが終了するときに呼び出されます。

重要なポイントとして、静的グローバル変数のコンストラクタが呼び出される順序は不定であることに注意して下さい。 同じソースファイル内にある静的グローバル変数のコンストラクタは、記述した順序で呼び出されることになっていますが、 他のソースファイルにある静的グローバル変数との間での順序関係は不定です。 ですから、順序関係に依存する処理を書いてはいけません。
なお、デストラクタは、コンストラクタが呼び出された順序の逆順で呼び出されることが保証されています。 コンストラクタの方の呼び出し順序が不定なので、結局のところ、デストラクタの呼び出し順も不定であるということになります。

静的グローバル関数

ソースファイル中で、staticキーワードを付けて宣言される関数は、静的グローバル関数になります。 静的グローバル変数と同様、これは同ファイル内からしかアクセスできなくなります。

静的グローバル変数が、staticキーワードを使うよりも、無名名前空間を使うことを推奨されているように、 静的グローバル関数も、無名名前空間を使う方が良いです。


練習問題

問題@ 静的メンバ変数が、オブジェクトのサイズに影響を与えないことを確認して下さい。

問題A 次のプログラムの実行結果を答えて下さい。

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
	MyClass(int value);
	~MyClass();
	
	inline int GetValue() const
	{
		return mValue;
	}
	
	static inline int GetObjectCount()
	{
		return msObjectCount;
	}

private:
	int mValue;
	static int msObjectCount;
};

#endif
// MyClass.cpp

#include "MyClass.h"

int MyClass::msObjectCount = 0;

MyClass::MyClass(int value) :
	mValue(value)
{
	++msObjectCount;
}

MyClass::~MyClass()
{
	--msObjectCount;
}
// main.cpp

#include "MyClass.h"

MyClass* func(int value)
{
	static MyClass c(value);
	
	MyClass* p = new MyClass(value);
	
	std::cout << MyClass::GetObjectCount() << " "
	          << c.GetValue() << " "
	          << p->GetValue() << " " << std::endl;
	
	return p;
}

int main()
{
	static const int LOOP_COUNT = 3;

	MyClass* c[LOOP_COUNT];

	for (int i = 0; i < LOOP_COUNT; ++i) {
		c[i] = func(i);
	}

	for (int i = 0; i < LOOP_COUNT; ++i) {
		delete c[i];
	}
}

問題B あるクラスがインスタンス化されたときに、各オブジェクトに重複の無い個別の ID (整数値) を割り当てたいとします。 静的メンバ変数を利用して、このような割り当てを自動化できるようにクラスを設計して下さい。


解答ページはこちら

参考リンク

更新履歴

'2014/5/24 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ