C++のグローバル変数とは、一般に関数の外側で宣言される変数のことです。
たとえば、次の count はグローバル変数です。
#include <iostream>
int count = 0; // グローバル変数
void increment() {
count++;
}
int main() {
increment();
std::cout << count << std::endl; // 1
}
count は main() の中ではなく、関数の外側で宣言されています。
そのため、increment() からも main() からも参照できます。
ただし、厳密にいうと「どこからでも参照できる」とは限りません。
C++では、グローバル変数の見える範囲は、スコープやリンケージによって変わります。
より正確には、グローバル変数は次のように説明できます。
C++で一般にグローバル変数と呼ばれるものは、名前空間スコープで宣言され、静的記憶域期間を持つ変数である。
少し難しく聞こえるかもしれませんが、最初は「関数の外側にある変数」と理解して問題ありません。
グローバル変数とローカル変数の違い
グローバル変数とローカル変数の大きな違いは、宣言する場所、使える範囲、寿命です。
int globalValue = 10; // グローバル変数
int main() {
int localValue = 20; // ローカル変数
}
globalValue は関数の外側で宣言されているため、グローバル変数です。
一方、localValue は main() の中で宣言されているため、ローカル変数です。
| 種類 | 宣言場所 | 使える範囲 | 寿命 |
|---|---|---|---|
| グローバル変数 | 関数の外側 | スコープやリンケージによって変わる | プログラム終了まで |
| ローカル変数 | 関数やブロックの中 | 宣言されたブロック内 | ブロックを抜けるまで |
グローバル変数は、プログラムの実行期間中ずっと存在します。
一方、ローカル変数は関数やブロックの実行中だけ存在します。
グローバル変数の基本的な使い方
複数の関数から同じ変数を使える
グローバル変数は、複数の関数から同じ値を共有できます。
#include <iostream>
int score = 100; // グローバル変数
void showScore() {
std::cout << score << std::endl;
}
int main() {
showScore();
score += 50;
showScore();
return 0;
}
このコードの出力は次のようになります。
100
150
score は showScore() の中でも、main() の中でも使えます。
関数が終わっても値が残る
グローバル変数は、関数の実行が終わっても値が消えません。
#include <iostream>
int counter = 0;
void countUp() {
counter++;
std::cout << counter << std::endl;
}
int main() {
countUp(); // 1
countUp(); // 2
countUp(); // 3
}
counter はグローバル変数なので、countUp() が終わっても値が保持されます。
グローバル変数の寿命
静的記憶域期間を持つ
グローバル変数は、静的記憶域期間を持ちます。
静的記憶域期間とは、簡単にいうと、プログラムの実行中ずっと存在するという意味です。
名前空間スコープで宣言されたグローバル変数は、通常 main() が始まる前に初期化され、プログラム終了時に破棄されます。
int value = 10;
int main() {
// value は main() が始まる前に準備されている
}
初期化には種類がある
グローバル変数の初期化には、いくつかの種類があります。
| 初期化の種類 | 例 | 概要 |
|---|---|---|
| ゼロ初期化 | int x; | 明示的な初期化がない場合に0相当で初期化される |
| 定数初期化 | int x = 10; | コンパイル時に決められる値で初期化される |
| 動的初期化 | std::string s = "hello"; | 実行時の処理を伴って初期化される |
通常のグローバル変数は main() より前に初期化されますが、別々の .cpp ファイルにあるグローバル変数同士の初期化順序には注意が必要です。
この問題は、後ほど「静的初期化順序問題」として説明します。
グローバル変数の初期値
未初期化のグローバル変数は0になる
グローバル変数は、明示的に初期化しなくても自動的にゼロ初期化されます。
#include <iostream>
int x; // 0
double y; // 0.0
bool flag; // false
char c; // '\0'
int main() {
std::cout << x << std::endl; // 0
}
これは、グローバル変数が静的記憶域期間を持つためです。
ローカル変数は自動で0にならない
一方、通常のローカル変数は、自動的には0になりません。
#include <iostream>
int main() {
int x;
std::cout << x << std::endl; // 不定値
}
未初期化のローカル変数を読むと、不定値になります。
場合によっては未定義動作につながるため、ローカル変数は必ず初期化して使うべきです。
int main() {
int x = 0;
}
スコープとリンケージ
グローバル変数を正しく理解するには、スコープとリンケージを押さえる必要があります。
スコープとは
スコープとは、その名前を使える範囲のことです。
#include <iostream>
int value = 10;
void func() {
std::cout << value << std::endl;
}
この value は、同じファイル内で宣言より後ろにあるコードから参照できます。
ただし、宣言より前では使えません。
#include <iostream>
void func() {
std::cout << value << std::endl; // エラー
}
int value = 10;
int main() {
func();
}
func() の時点では、まだ value が宣言されていないためです。
同じファイル内であれば、単純に定義を前に移動するのが一般的です。
#include <iostream>
int value = 10;
void func() {
std::cout << value << std::endl;
}
int main() {
func();
}
また、前方宣言として extern を使うこともできます。
#include <iostream>
extern int value;
void func() {
std::cout << value << std::endl;
}
int value = 10;
int main() {
func();
}
ただし、extern は主に別の .cpp ファイルにある変数を参照するために使われます。
リンケージとは
リンケージとは、別の翻訳単位、つまり別の .cpp ファイルから同じ名前を参照できるかどうかの仕組みです。
通常のグローバル変数は、外部リンケージを持ちます。
int value = 10;
この value は、別の .cpp ファイルから extern を使って参照できます。
一方、static を付けたり、無名 namespace に入れたりすると、外部から参照できない内部リンケージになります。
externを使ったグローバル変数
複数ファイルでグローバル変数を共有する
複数の .cpp ファイルで同じグローバル変数を共有したい場合は、extern を使います。
たとえば、次のように書きます。
// globals.h
#pragma once
extern int sharedValue;
// globals.cpp
#include "globals.h"
int sharedValue = 100;
// main.cpp
#include <iostream>
#include "globals.h"
int main() {
std::cout << sharedValue << std::endl;
}
globals.h の
extern int sharedValue;
は、「sharedValue という変数がどこかに存在します」と知らせる宣言です。
実体は globals.cpp の
int sharedValue = 100;
です。
宣言と定義の違い
グローバル変数では、宣言と定義の違いが重要です。
定義とは、変数の実体を作るものです。
int value = 10;
また、次のように初期値を書かなくても、グローバル変数としては定義になります。
int value;
一方、宣言は「そういう変数が存在する」と知らせるだけです。
extern int value;
この場合、基本的に実体は作られません。
ただし、注意点があります。
extern int value; // 宣言
extern int value = 10; // 定義
extern が付いていても、初期化子があると定義になります。
そのため、ヘッダファイルに次のように書くのは避けるべきです。
// globals.h
extern int value = 10; // NG:定義になる
複数の .cpp ファイルでインクルードされると、多重定義の原因になります。
ヘッダファイルにグローバル変数を書くときの注意点
通常のグローバル変数定義をヘッダに書くのは危険
初心者がよくやりがちなミスに、ヘッダファイルへグローバル変数の定義を書いてしまうケースがあります。
// globals.h
int value = 10; // NG
これを複数の .cpp ファイルでインクルードすると、それぞれの翻訳単位に value の定義が作られます。
// main.cpp
#include "globals.h"
// other.cpp
#include "globals.h"
この場合、リンク時に次のようなエラーが出ることがあります。
multiple definition of `value`
正しい書き方はextern宣言とcppでの定義
共有したいグローバル変数は、ヘッダに宣言を書き、.cpp に定義を書きます。
// globals.h
#pragma once
extern int value;
// globals.cpp
#include "globals.h"
int value = 10;
// main.cpp
#include <iostream>
#include "globals.h"
int main() {
std::cout << value << std::endl;
}
この形なら、変数の実体は globals.cpp に1つだけです。
C++17以降のinline変数
inline変数ならヘッダに定義を書ける
C++17以降では、inline 変数を使えます。
// globals.h
#pragma once
inline int value = 10;
通常のグローバル変数定義をヘッダに書くと多重定義になりますが、inline 変数であれば、複数の .cpp ファイルからインクルードされても問題になりません。
C++17以降で、ヘッダに変数を定義したい場合に使える便利な仕組みです。
定数にはinline constexprがよく使われる
定数をヘッダに置きたい場合は、次の形がよく使われます。
// constants.h
#pragma once
inline constexpr int MaxSize = 100;
inline constexpr double Pi = 3.141592653589793;
名前空間に入れると、さらに名前の衝突を避けやすくなります。
// constants.h
#pragma once
namespace Constants {
inline constexpr int MaxSize = 100;
inline constexpr double Pi = 3.141592653589793;
}
使う側は次のように書きます。
std::cout << Constants::MaxSize << std::endl;
staticグローバル変数
staticを付けるとファイル内限定になる
グローバル変数に static を付けると、その変数はその .cpp ファイル内だけで使えるようになります。
static int value = 10;
この value は内部リンケージを持つため、別の .cpp ファイルから extern で参照できません。
// file1.cpp
static int value = 10;
// file2.cpp
extern int value; // file1.cpp の static int value は参照できない
static グローバル変数は、ファイル内だけで使う内部状態を持ちたい場合に利用できます。
変数だけならstaticでも問題ない
変数を1つだけファイル内に閉じたい場合、static を使っても問題ありません。
static int cache = 0;
ただし、C++では無名 namespace を使う方法もよく使われます。
無名namespaceによるファイル内限定
現代C++では無名namespaceもよく使われる
C++では、ファイル内だけで使いたい変数や関数を、無名 namespace に入れることがあります。
namespace {
int value = 10;
}
void func() {
value++;
}
この value は、その .cpp ファイル内だけで参照できます。
変数・関数・型をまとめて隠せる
無名 namespace の利点は、変数だけでなく、関数や型もまとめてファイル内限定にできることです。
namespace {
int cache = 0;
void helper() {
// このcpp内だけで使う補助関数
}
struct InternalData {
int value;
};
}
このように、外部に公開したくない実装詳細をまとめて隠せます。
constグローバル変数
名前空間スコープのconstは通常内部リンケージになる
C++では、名前空間スコープの const 変数は、通常は内部リンケージになります。
const int MaxSize = 100;
これは、その翻訳単位内で使われる定数として扱われます。
ヘッダファイルに次のように書いても、多くの場合は問題になりません。
// constants.h
#pragma once
constexpr int MaxSize = 100;
constexpr 変数は const でもあるため、通常は内部リンケージになります。
外部リンケージにしたい場合はextern constを使う
const 変数を複数ファイルで同じ1つの実体として共有したい場合は、extern const を使います。
// constants.h
#pragma once
extern const int MaxSize;
// constants.cpp
#include "constants.h"
extern const int MaxSize = 100;
ただし、現代C++では、ヘッダに置く定数なら次の形の方が簡潔です。
inline constexpr int MaxSize = 100;
グローバル変数と名前空間
名前の衝突を避けるために名前空間を使う
グローバル変数をグローバル名前空間にそのまま置くと、名前の衝突が起きやすくなります。
int count = 0;
int mode = 1;
int status = 0;
大きなプログラムでは、どの count なのか、どの mode なのかが分かりにくくなります。
そのため、名前空間に入れることがあります。
namespace AppConfig {
int windowWidth = 1280;
int windowHeight = 720;
}
使う側は次のように書きます。
std::cout << AppConfig::windowWidth << std::endl;
名前空間に入れてもグローバル状態であることは変わらない
名前空間に入れると名前の衝突は避けやすくなります。
ただし、変数がプログラム全体にわたって存在する共有状態であることは変わりません。
namespace AppState {
int currentMode = 0;
}
このような変更可能な状態を広い範囲から触れるようにすると、保守しにくくなる場合があります。
名前空間は名前の整理には役立ちますが、設計上の依存関係をなくすものではありません。
クラスのstaticメンバ変数との違い
staticメンバ変数はクラスに属する共有変数
グローバル変数に似たものとして、クラスの static メンバ変数があります。
class Counter {
public:
static int count;
};
int Counter::count = 0;
Counter::count は、Counter クラスに属する共有変数です。
Counter::count++;
グローバル変数のようにプログラム中で共有されますが、名前がクラスに属しているため、意味が明確になりやすいです。
C++17以降はinline staticが使える
C++17以降では、クラス内で inline static として定義できます。
class Counter {
public:
inline static int count = 0;
};
ただし、実務では public な static 変数を直接公開するより、関数を通して操作する方が安全です。
class Counter {
private:
inline static int count = 0;
public:
static void increment() {
++count;
}
static int getCount() {
return count;
}
};
この形なら、値の変更方法をクラス側で制御できます。
グローバル変数のメリット
複数の関数から共有しやすい
グローバル変数のメリットは、複数の関数から同じ値を簡単に使えることです。
int difficulty = 2;
void enemyAI() {
if (difficulty > 1) {
// 難しい動作
}
}
void bossAI() {
if (difficulty > 2) {
// さらに難しい動作
}
}
引数で毎回渡さなくても、複数の関数から同じ値を参照できます。
小さなプログラムでは書きやすい
学習用コードや小規模なプログラムでは、グローバル変数を使うと簡単に状態を共有できます。
int score = 0;
int life = 3;
ただし、プログラムが大きくなるほど、どこで値が変更されたのか分かりにくくなります。
定数の共有には向いている
変更されない定数であれば、グローバルに置いても問題になりにくいです。
inline constexpr int MaxRetryCount = 3;
inline constexpr double TimeoutSeconds = 5.0;
このような定数は値が変わらないため、変更可能なグローバル変数より安全です。
グローバル変数のデメリット
どこで変更されたかわかりにくい
変更可能なグローバル変数は、プログラムのさまざまな場所から変更できます。
int score = 0;
void addScore() {
score += 10;
}
void resetGame() {
score = 0;
}
void debug() {
score = 9999;
}
このようなコードでは、score の値がおかしくなったときに、どこで変更されたのか追いにくくなります。
関数の依存関係が見えにくい
次の関数を見てください。
int taxRate = 10;
int calculatePrice(int price) {
return price + price * taxRate / 100;
}
一見すると、calculatePrice() は price だけに依存しているように見えます。
しかし実際には、外側の taxRate に依存しています。
より分かりやすい設計にするなら、必要な値を引数で渡します。
int calculatePrice(int price, int taxRate) {
return price + price * taxRate / 100;
}
この方が、関数の入力と出力の関係が明確になります。
テストしにくくなる
グローバル変数を使うと、テストごとに状態をリセットする必要が出ることがあります。
int counter = 0;
void increment() {
counter++;
}
テストの順番によって結果が変わると、原因を追いにくくなります。
void test1() {
counter = 0;
increment();
// counter == 1 を期待
}
void test2() {
counter = 0;
increment();
increment();
// counter == 2 を期待
}
グローバル状態が増えるほど、テストの独立性が下がります。
マルチスレッドで競合しやすい
複数のスレッドから同じグローバル変数を同時に変更すると、データ競合が起きる可能性があります。
int counter = 0;
void increment() {
counter++;
}
counter++ は一見1つの処理に見えますが、実際には次のような処理に分かれます。
- 値を読む
- 1を足す
- 書き戻す
複数スレッドが同時に実行すると、正しく値が増えないことがあります。
単純なカウンタであれば、std::atomic が使えます。
#include <atomic>
std::atomic<int> counter = 0;
void increment() {
counter++;
}
ただし、std::atomic は万能ではありません。
複数の値をまとめて一貫性のある状態として扱いたい場合は、std::mutex などを使う必要があります。
#include <mutex>
#include <string>
std::mutex mtx;
int score = 0;
std::string playerName;
void updatePlayer(const std::string& name, int newScore) {
std::lock_guard<std::mutex> lock(mtx);
playerName = name;
score = newScore;
}
静的初期化順序問題
別ファイルのグローバル変数同士は初期化順序に注意
C++のグローバル変数で特に注意したいのが、静的初期化順序問題です。
別々の .cpp ファイルにあるグローバル変数の動的初期化順序は、期待通りにならないことがあります。
// file1.cpp
#include <string>
std::string name = "Alice";
// file2.cpp
#include <string>
extern std::string name;
std::string message = "Hello, " + name;
この場合、message が初期化される時点で、name の初期化がまだ終わっていない可能性があります。
このような問題を静的初期化順序問題と呼びます。
関数内staticを使うと回避しやすい
対策として、関数内 static を使う方法があります。
#include <string>
const std::string& getName() {
static const std::string name = "Alice";
return name;
}
関数内 static 変数は、その関数が初めて呼ばれたときに初期化されます。
依存するオブジェクトも含めて関数内 static にすると、初期化順序問題を避けやすくなります。
#include <string>
const std::string& getName() {
static const std::string name = "Alice";
return name;
}
const std::string& getMessage() {
static const std::string message = "Hello, " + getName();
return message;
}
このようにすれば、必要になったタイミングで安全に初期化されます。
関数内static変数との違い
寿命は長いがスコープは狭い
関数内 static 変数も、グローバル変数と同じく静的記憶域期間を持ちます。
void func() {
static int count = 0;
count++;
}
この count は、func() の呼び出しが終わっても値を保持します。
ただし、スコープは func() の中だけです。
外部の関数から直接触ることはできません。
状態を閉じ込めたい場合に便利
次のグローバル変数は、どの関数からでも直接変更できてしまいます。
int count = 0;
一方、関数内 static にすると、状態を関数の中に閉じ込められます。
int nextId() {
static int id = 0;
return ++id;
}
この id は nextId() の中でしか触れません。
| 種類 | スコープ | 寿命 |
|---|---|---|
| グローバル変数 | 広い | プログラム終了まで |
| 関数内static変数 | 関数内のみ | プログラム終了まで |
状態を必要以上に広く公開したくない場合は、関数内 static の方が安全です。
C++20のconstinit
constinitは静的初期化を強制する
C++20以降では、constinit という指定子が使えます。
constinit int value = 10;
constinit は、その変数が静的初期化できることをコンパイラに確認させるためのものです。
実行時にしか決まらない値で初期化しようとすると、エラーになります。
int getValue();
constinit int value = getValue(); // エラーになる
constinitはconstとは違う
constinit は const とは違います。
constinit int value = 10;
int main() {
value = 20; // 変更可能
}
違いを整理すると、次のようになります。
| 指定子 | 意味 |
|---|---|
const | 値を変更できない |
constexpr | コンパイル時定数として扱える |
constinit | 静的初期化されることを強制する |
グローバル変数の初期化順序問題を意識する場合、constinit は知っておくと役立ちます。
thread_localグローバル変数
スレッドごとに別々の変数を持てる
C++には thread_local という指定子もあります。
thread_local int counter = 0;
通常のグローバル変数は、すべてのスレッドで共有されます。
int counter = 0;
一方、thread_local を付けると、スレッドごとに別々の counter を持ちます。
thread_local int counter = 0;
全体共有ではなくスレッド単位の状態に使う
thread_local は、スレッドごとに独立した状態を持ちたい場合に使います。
たとえば、スレッドごとの一時バッファや、スレッドごとのカウンタなどです。
ただし、thread_local もグローバルな寿命を持つ状態であることには変わりません。
使いすぎると設計が分かりにくくなるため、必要な場面に限定して使うべきです。
グローバル変数を使ってよいケース
変更されない定数
変更されない定数は、グローバルに置いても問題になりにくいです。
inline constexpr int MaxRetryCount = 3;
inline constexpr double TimeoutSeconds = 5.0;
名前空間に入れると、より整理しやすくなります。
namespace Constants {
inline constexpr int MaxRetryCount = 3;
inline constexpr double TimeoutSeconds = 5.0;
}
ファイル内だけで使う内部状態
その .cpp ファイルの内部実装として使う状態であれば、無名 namespace に閉じ込める方法があります。
namespace {
int cachedValue = 0;
}
int getValue() {
return cachedValue;
}
void setValue(int value) {
cachedValue = value;
}
外部から直接触れないようにすることで、影響範囲を限定できます。
プログラム全体で1つだけ必要なもの
ログ出力、設定管理、メモリアロケータなど、設計上プログラム全体で1つだけ必要なものは、グローバルに近い形で扱われることがあります。
ただし、直接グローバル変数として公開するより、関数やクラスで管理する方が安全です。
Logger& getLogger() {
static Logger logger;
return logger;
}
このように関数を通してアクセスすれば、初期化タイミングも管理しやすくなります。
グローバル変数を避ける方法
必要な値を引数で渡す
グローバル変数を使わず、必要な値を関数の引数として渡す方法があります。
int calculateDamage(int attack, int defense) {
return attack - defense;
}
引数で渡すことで、その関数が何に依存しているのかが明確になります。
クラスに状態を持たせる
状態をクラスに閉じ込める方法もあります。
class Game {
private:
int score = 0;
public:
void addScore(int value) {
score += value;
}
int getScore() const {
return score;
}
};
この形なら、score は Game オブジェクトの中で管理されます。
外部から勝手に変更されにくくなり、保守しやすくなります。
設定オブジェクトを渡す
設定値をグローバルに置くのではなく、設定オブジェクトとして渡す方法もあります。
struct Config {
int width;
int height;
bool fullscreen;
};
void initializeWindow(const Config& config) {
// config を使う
}
この方法なら、関数がどの設定に依存しているのかが分かりやすくなります。
アクセス関数で制御する
どうしても共有状態が必要な場合でも、直接グローバル変数を公開するのではなく、関数を通してアクセスさせる方法があります。
namespace {
int score = 0;
}
int getScore() {
return score;
}
void addScore(int value) {
score += value;
}
この形なら、外部から score = -999; のように勝手に変更されることを防げます。
代表的な書き方まとめ
ヘッダに通常のグローバル変数定義を書くのは避ける
// globals.h
int value = 10; // NG
複数の .cpp からインクルードされると、多重定義の原因になります。
複数ファイルで共有するならexternを使う
// globals.h
#pragma once
extern int value;
// globals.cpp
#include "globals.h"
int value = 10;
C++17以降ならinline変数を使える
// globals.h
#pragma once
inline int value = 10;
定数ならinline constexprが使いやすい
// constants.h
#pragma once
namespace Constants {
inline constexpr int MaxSize = 100;
}
ファイル内限定なら無名namespaceを使う
namespace {
int internalState = 0;
}
クラスに属する共有値ならinline staticを使える
class Counter {
public:
inline static int count = 0;
};
グローバル変数の使い分け目安
| 目的 | おすすめ |
|---|---|
| 変更されない定数を共有したい | inline constexpr |
| 複数ファイルで変更可能な変数を共有したい | ヘッダに extern、.cpp に定義 |
| ヘッダだけで変数を定義したい | C++17以降の inline 変数 |
1つの .cpp ファイル内だけで使いたい | 無名 namespace または static |
| クラス全体で共有したい | inline static メンバ変数 |
| 状態を安全に管理したい | クラスや関数でカプセル化 |
| 単純な共有カウンタをスレッドセーフにしたい | std::atomic |
| 複数の共有状態をまとめて守りたい | std::mutex |
| スレッドごとに状態を持ちたい | thread_local |
| 静的初期化を保証したい | C++20以降の constinit |
初心者がまず覚えるべきポイント
グローバル変数は関数の外側で宣言する
int value = 10;
このように関数の外側で宣言された変数が、一般にグローバル変数と呼ばれます。
プログラム終了まで存在する
グローバル変数は静的記憶域期間を持つため、プログラムの終了まで存在します。
未初期化でも0になる
int value; // 0
グローバル変数は明示的に初期化しなくてもゼロ初期化されます。
ヘッダに普通の定義を書かない
// globals.h
int value = 10; // NG
共有したい場合は、基本的に次の形にします。
// globals.h
extern int value;
// globals.cpp
int value = 10;
変更可能なグローバル変数は乱用しない
変更可能なグローバル変数は、どこで値が変わったのか分かりにくくなります。
そのため、実務ではなるべくクラスや関数の中に状態を閉じ込める設計が好まれます。
実務での考え方
問題になりやすいのは変更可能なグローバル状態
実務で特に問題になりやすいのは、グローバル変数そのものというより、変更可能なグローバル状態です。
int currentMode = 0;
std::string currentUserName;
std::vector<int> cache;
このような変数は、プログラムのさまざまな場所から変更される可能性があります。
その結果、次のような問題が起きやすくなります。
- どこで変更されたかわからない
- テストの順番に依存する
- マルチスレッドで競合する
- 初期化や終了処理の順序でバグが起きる
- 関数の依存関係が隠れる
- 再利用しにくくなる
グローバル定数は比較的安全
一方、変更されない定数は問題になりにくいです。
inline constexpr int MaxRetryCount = 3;
inline constexpr double TimeoutSeconds = 5.0;
値が変わらないため、どこかで勝手に変更される心配がありません。
つまり、次のように分けて考えると分かりやすいです。
| 種類 | 危険度 |
|---|---|
| グローバル定数 | 比較的安全 |
| 変更可能なグローバル変数 | 注意が必要 |
| 複数スレッドから変更されるグローバル変数 | 特に注意が必要 |
まとめ
C++のグローバル変数は、一般に関数の外側で宣言される変数です。
静的記憶域期間を持つため、プログラムの開始から終了まで存在します。
ただし、実際にどこから参照できるかは、スコープやリンケージによって変わります。
複数ファイルで共有したい場合は、ヘッダに extern 宣言を書き、.cpp に定義を書くのが基本です。
// globals.h
extern int value;
// globals.cpp
int value = 10;
C++17以降では、ヘッダに定義を書ける inline 変数も使えます。
inline int value = 10;
定数であれば、次のような inline constexpr がよく使われます。
inline constexpr int MaxSize = 100;
グローバル変数は便利ですが、変更可能なグローバル状態を増やすと、保守性やテスト性が下がります。
そのため、実務では次のような考え方が重要です。
- 定数はグローバルに置いても問題になりにくい
- 変更可能な状態はできるだけ狭い範囲に閉じ込める
- 複数ファイルで共有するなら
externやinlineを正しく使う - ファイル内限定なら
staticや無名 namespace を使う - 初期化順序問題に注意する
- マルチスレッドでは
std::atomicやstd::mutexを検討する
C++のグローバル変数は、単に「関数の外にある変数」と覚えるだけでは不十分です。
実務レベルでは、静的記憶域期間、スコープ、リンケージ、初期化順序の4つをセットで理解することが大切です。
以上、C++のグローバル変数についてでした。
最後までお読みいただき、ありがとうございました。
