C++プログラミングにおいて、staticキーワードは非常に重要な役割を果たします。このキーワードは、変数や関数のストレージ期間(寿命)とリンケージ(可視性)を制御するために使用されます。本稿では、staticキーワードの様々な使用方法と具体的な実装例を詳しく解説します。
1. 関数内の静的ローカル変数
関数内で宣言される静的ローカル変数は、関数の実行中だけでなく、プログラム全体の実行期間を通じて保持されます。最初の関数呼び出し時に一度だけ初期化され、その後の呼び出しでは前の値が維持されます。
#include <iostream>
void incrementCounter() {
static int hiddenValue = 0;
hiddenValue += 1;
std::cout << "現在の値: " << hiddenValue << std::endl;
}
int main() {
incrementCounter();
incrementCounter();
incrementCounter();
return 0;
}
実行結果:
現在の値: 1
現在の値: 2
現在の値: 3
この例では、hiddenValueは3回の関数呼び出し間で値を保持し、各呼び出しでインクリメントされています。
2. クラスの静的メンバー変数と静的メンバー関数
クラスの静的メンバーは、クラスのインスタンス(オブジェクト)ではなく、クラス自体に属します。すべてのオブジェクト間で共有され、クラス名を使って直接アクセスできます。
#include <iostream>
class DataRepository {
public:
static int sharedCounter;
static void displayStatus() {
std::cout << "共有カウンター: " << sharedCounter << std::endl;
}
};
int DataRepository::sharedCounter = 0;
int main() {
DataRepository::sharedCounter = 42;
DataRepository::displayStatus();
DataRepository first, second;
first.sharedCounter = 100;
DataRepository::displayStatus();
return 0;
}
実行結果:
共有カウンター: 42
共有カウンター: 100
重要な点として、静的メンバー関数は静的メンバー変数のみにアクセスできます。非静的メンバー変数へのアクセスはコンパイルエラーとなります。
3. ファイルスコープでのstatic宣言
グローバル変数や関数の前にstaticを付けると、それらを内部リンケージにできます。これは、当該ファイル内でのみアクセス可能となり、他の翻訳単位(ソースファイル)と名前が衝突する心配がなくなります。
#include <iostream>
namespace {
int moduleInternalValue = 500;
void internalProcess() {
std::cout << "内部プロセス: " << moduleInternalValue << std::endl;
}
}
int main() {
internalProcess();
return 0;
}
この手法は、モジュール間のデータ隠蔽を実現し、適切なカプセル化を促進します。
4. 静的メンバー変数の初期化タイミング
静的メンバー変数の宣言はクラス内で行いますが、実際のメモリ割り当てと初期化はクラス定義の外部で必要です。これは、静的メンバーがクラスに属し、個々のオブジェクトに属さないためです。
#include <iostream>
class InstanceTracker {
private:
static int objectCount;
int instanceId;
public:
InstanceTracker() {
instanceId = ++objectCount;
}
static void showTotalInstances() {
std::cout << "総インスタンス数: " << objectCount << std::endl;
}
void showInstanceInfo() const {
std::cout << "インスタンスID: " << instanceId << std::endl;
}
};
int InstanceTracker::objectCount = 0;
int main() {
InstanceTracker a;
InstanceTracker b;
InstanceTracker c;
InstanceTracker::showTotalInstances();
a.showInstanceInfo();
b.showInstanceInfo();
return 0;
}
実行結果:
総インスタンス数: 3
インスタンスID: 1
インスタンスID: 2
5. 静的ローカル変数の初期化タイミング
C++11以降、静的ローカル変数はスレッドセーフに初期化されます。これは静的な初期化Cleanup問題(CWE-667)を解決する重要な機能です。
#include <iostream>
class HeavyResource {
public:
HeavyResource() {
std::cout << "リソース初期化" << std::endl;
}
};
void safeInitializer() {
static HeavyResource resource;
std::cout << "関数呼び出し" << std::endl;
}
int main() {
std::cout << "開始" << std::endl;
safeInitializer();
safeInitializer();
std::cout << "終了" << std::endl;
return 0;
}
実行結果:
開始
リソース初期化
関数呼び出し
関数呼び出し
終了
注目すべきは、リソース初期化が最初の呼び出し時に一度だけ行われ、その後の呼び出しでは初期化処理がスキップされることです。
使用場面のまとめ
| 宣言場所 | 用途 | スコープ | 寿命 |
|---|---|---|---|
| 関数内 | 関数呼び出し間で値を保持 | 関数内 | プログラム終了まで |
| クラス内 | クラス全体で共有するデータ | クラス内 | プログラム終了まで |
| ファイルスコープ | モジュール内の情報隠蔽 | ファイル内 | プログラム終了まで |