C++ ストレージクラス

ストレージクラスは、C++ プログラムにおける変数/関数のスコープ(可視性)とライフサイクルを定義します。これらの指定子は、修飾する型の前に置かれます。以下に、C++ プログラムで使用できるストレージクラスを示します:

  • auto:これはデフォルトのストレージクラス指定子であり、通常は省略できます。auto で指定された変数は自動ストレージ期間を持ち、つまりそのライフサイクルは定義されたブロックに限定されます。auto 変数は通常スタック上に割り当てられます。

  • register:コンパイラに変数を CPU レジスタに格納してアクセス速度を向上させるよう提案するために使用されます。C++11 以降では、register は非推奨の機能であり、実際の効果はありません。

  • static:静的ストレージ期間を持つ変数や関数を定義するために使用され、そのライフサイクルはプログラムの実行全体にわたります。関数内部では、static 変数の値は関数呼び出しの間で保持されます。ファイル内部またはグローバルスコープでは、static 変数は内部リンケージを持ち、定義されたファイル内でのみアクセスできます。

  • extern:外部リンケージを持つ変数や関数を宣言するために使用され、複数のファイル間で共有できます。デフォルトでは、グローバル変数と関数は extern ストレージクラスを持ちます。あるファイルで extern を使用して別のファイルで定義されたグローバル変数や関数を宣言すると、ファイル間で共有できます。

  • mutable (C++11):クラスのメンバ変数を修飾するために使用され、const メンバ関数内でこれらの変数の値を変更できるようにします。通常、キャッシュやカウンタなど、const コンテキストで変更する必要があるデータに使用されます。

  • thread_local (C++11):スレッドローカルストレージ期間を持つ変数を定義するために使用され、各スレッドが独自の独立したコピーを持ちます。スレッドローカル変数のライフサイクルはスレッドのライフサイクルと同じです。

C++17 以降、auto キーワードは C++ のストレージクラス指定子ではなくなり、register キーワードは非推奨になりました。

ストレージクラス指定子は、プログラマに変数と関数のライフサイクルおよび可視性を制御する手段を提供します。

ストレージクラス指定子を適切に使用すると、プログラムの保守性とパフォーマンスを向上させることができます。

C++11 以降、register は本来の役割を失い、mutable と thread_local は特定のプログラミング問題を解決するために新しく導入された機能です。

以下は、さまざまなストレージクラス指定子を示す例です:

例

#include <iostream>

// グローバル変数、外部リンケージを持ち、デフォルトのストレージクラスは extern
int globalVar;

void function() {
    // ローカル変数、自動ストレージ期間を持ち、デフォルトのストレージクラスは auto
    auto int localVar = 10;

    // 静的変数、静的ストレージ期間を持ち、ライフサイクルがプログラム全体にわたる
    static int staticVar = 20;

    const int constVar = 30; // const 変数はデフォルトで static ストレージ期間を持つ

    // const 変数を変更しようとすると、コンパイルエラーになる
    // constVar = 40;

    // mutable メンバ変数。const メンバ関数内で変更できる
    class MyClass {
    public:
        mutable int mutableVar;

        void constMemberFunc() const {
            mutableVar = 50; // mutable メンバ変数の変更を許可
        }
    };

    // スレッドローカル変数。各スレッドが独自のコピーを持つ
    thread_local int threadVar = 60;
}

int main() {
    extern int externalVar; // 外部リンケージを持つ変数を宣言

    function();

    return 0;
}

auto ストレージクラス

C++11 以降、autoauto キーワードは、変数宣言時に初期化式から変数の型を自動推定する場合と、関数宣言時に関数の戻り値のプレースホルダとして使用する場合の2つのケースで使用されます。

C++98 標準では auto キーワードは自動変数の宣言に使用されていましたが、使用が極めて少なく冗長であるため、C++17 でこの用法は削除されました。

初期化式に基づいて宣言される変数の型を自動推定します。例:

auto f=3.14; //double auto s("hello"); //const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 5.0, x3='r';// エラー、同じ型に初期化する必要があります

register ストレージクラス

register はストレージクラスの一種で、変数を宣言し、コンパイラにこれらの変数をレジスタに格納して高速にアクセスできるようにヒントを与えます。

register キーワードを使用すると、メモリへのアクセス回数が減るため、プログラムの実行速度を向上させることができます。

ただし、register ストレージクラスは単なるヒントであり、コンパイラはそれを無視できることに注意が必要です。現代のコンパイラは通常、コードを自動的に最適化して適切な記憶場所を選択するためです。

構文形式:

register data_type variable_name;
  • registerストレージクラスのキーワードであり、コンパイラに変数をレジスタに格納するようヒントを与えるために使用されます。
  • data_type変数のデータ型であり、任意の有効な C++ データ型を使用できます。
  • variable_name変数の名前です。
void loop() { register int i; for (i = 0; i < 1000; ++i) { // ループ本体 } }

register ストレージクラスは、アクセス速度を向上させるためにコンパイラに変数をレジスタに格納するようヒントを与えるために使用されます。しかし、現代のコンパイラの自動最適化能力により、register キーワードを使用する必要はなく、実際にはほとんど使用されません。

C++11 標準では、register キーワードはもはやストレージクラス指定子ではなく、非推奨の機能です。つまり、C++11 以降のバージョンで register キーワードを使用しても、プログラムに何の影響も与えません。

C++ では、特に大きなデータ構造を扱う場合に、参照やポインタを使用してアクセス速度を向上させることができます。

static ストレージクラス

staticstatic ストレージクラスは、コンパイラにプログラムのライフサイクル中ローカル変数を保持させ、スコープに入るたびに生成・破棄する必要をなくします。したがって、static を使用してローカル変数を修飾すると、関数呼び出し間でローカル変数の値を保持できます。

static 修飾子はグローバル変数にも適用できます。static がグローバル変数を修飾すると、変数のスコープが宣言されたファイル内に制限されます。

C++ では、static をクラスのデータメンバに使用すると、そのメンバのコピーが1つだけになり、クラスのすべてのオブジェクトで共有されます。

例

#include <iostream> // 関数宣言 void func(void); static int count = 10; /*グローバル変数*/ int main() { while(count--) { func(); } return 0; } // 関数定義 void func( void ) { static int i = 5; // ローカル静的変数 i++; std::cout << "変数 i は" << i ; std::cout << "、変数 count は" << count << std::endl; }

上記のコードをコンパイルして実行すると、次の結果が生成されます:

变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0

extern ストレージクラス

externextern ストレージクラスは、グローバル変数の参照を提供するために使用され、グローバル変数はすべてのプログラムファイルから見えます。'extern' を使用する場合、初期化できない変数については、変数名が以前に定義された記憶場所を指すようにします。

複数のファイルがあり、他のファイルで使用できるグローバル変数や関数を定義した場合、他のファイルで extern を使用してextern定義済みの変数や関数への参照を取得できます。次のように理解できます:externextern は別のファイルでグローバル変数や関数を宣言するために使用されます。

extern 修飾子は通常、2つ以上のファイルが同じグローバル変数や関数を共有する場合に使用されます。次に示すとおりです:

最初のファイル:main.cpp

例

#include <iostream> int count ; extern void write_extern(); int main() { count = 5; write_extern(); }

2番目のファイル:support.cpp

例

#include <iostream> extern int count; void write_extern(void) { std::cout << "Count is " << count << std::endl; }

ここで、2番目のファイル内のexternextern キーワードは、最初のファイル main.cpp で定義された count を宣言するために使用されます。では、これら2つのファイルを次のようにコンパイルします:

$ g++ main.cpp support.cpp -o write

これにより、write実行可能プログラムが生成されます。実行してみると、write次の結果が生成されます:

$ ./write
Count is 5

mutable ストレージクラス

mutable はキーワードであり、クラスのメンバ変数を修飾して、const メンバ関数内で変更できるようにするために使用されます。通常、const メンバ関数はオブジェクトの状態を変更できませんが、メンバ変数が mutable として宣言されている場合は、const 関数内で変更できます。

特徴:

  • 変更を許可:mutableメンバ変数はconstメンバ関数内で変更できます。
  • 設計目的:通常、オブジェクトの外部状態を変更せずに状態管理を行う必要があるシナリオ、例えばキャッシュや遅延計算などに使用されます。

例

#include <iostream>

class Example {
public:
    Example() : value(0), cachedValue(0) {}

    // 定数メンバ関数
    int getValue() const {
        return value; // 定数メンバの読み取り
    }

    // mutable メンバの変更
    void increment() {
        ++value;
        cachedValue = value * 2; // mutable メンバの変更
    }

    int getCachedValue() const {
        return cachedValue; // mutable メンバの読み取り
    }

private:
    int value;                 // 通常のメンバ。const 関数内では変更できない
    mutable int cachedValue;   // 変更可能なメンバ。const 関数内で変更できる
};

int main() {
    const Example ex;
    // ex.increment(); // エラー:const オブジェクトで非 const 関数を呼び出すことはできません
    // ex.value = 10; // エラー:const オブジェクトのメンバを変更することはできません

    std::cout << "Value: " << ex.getValue() << std::endl;
    std::cout << "Cached Value: " << ex.getCachedValue() << std::endl; // 出力は 0

    return 0;
}

適用シナリオ:

  • キャッシュ:〜ではconst関数内で結果を計算してキャッシュし、オブジェクトの外部状態に影響を与えない。
  • 状態追跡:ログカウンターなど、呼び出し回数などの情報を追跡し、クラスのロジックへの侵入的な変更を避ける。

注意事項:

  • mutable変数の使用は慎重に行うべきであり、予期しない状態変化を引き起こしてコードの可読性や保守性に影響を与えないようにする。
  • mutable次の環境で状態を変更する必要がある場合に適用され、const環境内で状態を変更する特定の状況に適用され、普遍的な設計パターンではありません。

thread_local ストレージクラス

thread_local は C++11 で導入されたストレージクラスであり、マルチスレッド環境でスレッド固有の変数を管理するために使用される。

thread_local で修飾された変数は各スレッドに独立したインスタンスを持ち、各スレッドがこの変数に対して行う操作は他のスレッドに影響を与えない。

  • 独立性:各スレッドが独自の変数コピーを持ち、異なるスレッド間の読み書き操作は互いに干渉しない。
  • ライフサイクル:thread_local変数はそのスレッドの終了時に自動的に破棄される。
  • 初期化:thread_local変数は静的初期化または動的初期化が可能で、宣言時に初期化することをサポートする。

thread_local は、スレッド状態の保存、キャッシュ、データ競合の回避が必要なシナリオ(スレッドプール、リクエストコンテキストなど)に適している。

以下は、thread_local として宣言できる変数の例です:

#include <iostream> #include <thread> thread_local int threadSpecificVar = 0; // 各スレッドが独自の threadSpecificVar を持つ void threadFunction(int id) { threadSpecificVar = id; // スレッド固有の変数を設定する std::cout << "Thread " << id << ": threadSpecificVar = " << threadSpecificVar << std::endl; } int main() { std::thread t1(threadFunction, 1); std::thread t2(threadFunction, 2); t1.join(); t2.join(); return 0; }

注意事項:

  • パフォーマンス:各スレッドが独立したコピーを持つため、thread_local変数へのアクセス速度はグローバル変数や静的変数よりも若干遅い場合がある。
  • 静的ストレージ:thread_local変数のストレージ型は静的ストレージ期間であり、プログラムの実行中ずっと存在し続ける。
その他の拡張