ストレージ クラス

C++ の変数宣言のコンテキストにおいて、ストレージ クラスは、オブジェクトの有効期間、リンケージ、およびメモリの場所を制御する型指定子です。 特定のオブジェクトはストレージ クラスを 1 つのみ持つことができます。 ブロック内で定義された変数は、externstatic、または thread_local 指定子によって指定されていない限り、自動ストレージを持ちます。 自動オブジェクトおよび変数にはリンケージがないため、ブロックの外側のコードには不可視です。 メモリは、実行がブロックに入ったときに自動的に割り当てられ、ブロックの終了時に割り当て解除されます。

メモ

  • mutable キーワードは、ストレージ クラス指定子と見なされることがあります。 ただし、クラス定義のメンバー一覧でのみ使用できます。

  • Visual Studio 2010 以降:auto キーワードは C++ のストレージ クラス指定子ではなくなりました。また、register キーワードは非推奨とされました。 Visual Studio 2017 バージョン 15.7 以降: (/std:c++17 モード以降で使用可能): register キーワードは、C++ 言語から削除されます。 その使用により、診断メッセージが発生します。

    // c5033.cpp
    // compile by using: cl /c /std:c++17 c5033.cpp
    register int value; // warning C5033: 'register' is no longer a supported storage class
    

static

static キーワードを使用すると、変数と関数をグローバル スコープ、名前空間スコープ、およびクラス スコープで宣言することができます。 静的変数は、ローカル スコープでも宣言できます。

静的存続期間は、オブジェクトまたは変数がプログラム起動時に割り当てられ、プログラムの終了時に解放されることを意味します。 外部リンケージは、変数が宣言されたファイルの外部から変数名を参照できることを意味します。 逆に、内部リンケージは、変数が宣言されたファイルの外部でその名前を参照できないことを意味します。 既定では、グローバル名前空間で定義されたオブジェクトまたは変数には静的存続期間と外部リンケージがあります。 static キーワードは次の状況で使用できます。

  1. ファイル スコープ (グローバル スコープまたは名前空間スコープ、あるいはその両方) で変数または関数を宣言する場合、static キーワードは、変数または関数に内部リンケージがあることを指定します。 変数を宣言すると、変数は静的存続期間が設定され、コンパイラによって 0 に初期化されます (別の値を指定していない場合)。

  2. 関数で変数を宣言する場合、static キーワードは、その関数の呼び出しと呼び出しの間に変数がその状態を保持することを指定します。

  3. クラス宣言でデータ メンバーを宣言する場合、static キーワードは、メンバーの 1 つのコピーがクラスのすべてのインスタンスで共有されることを指定します。 static データ メンバーはファイル スコープで定義する必要があります。 const static として宣言した整数データ メンバーには、初期化子を定義できます。

  4. クラス宣言でメンバー関数を宣言する場合、static キーワードは、関数がクラスのすべてのインスタンスで共有されることを指定します。 static メンバー関数には暗黙の this ポインターがないため、この関数からはインスタンス メンバーにアクセスできません。 インスタンス メンバーにアクセスするには、インスタンスのポインターまたは参照であるパラメーターを指定して関数を宣言します。

  5. union のメンバーを static として宣言することはできません。 ただし、グローバルに宣言された匿名の union は、明示的に宣言された static である必要があります。

この例では、関数内で static として宣言された変数が、その関数の呼び出しと呼び出しの間、どのようにその状態を保持するかを示します。

// static1.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
void showstat( int curr ) {
   static int nStatic;    // Value of nStatic is retained
                          // between each function call
   nStatic += curr;
   cout << "nStatic is " << nStatic << endl;
}

int main() {
   for ( int i = 0; i < 5; i++ )
      showstat( i );
}
nStatic is 0
nStatic is 1
nStatic is 3
nStatic is 6
nStatic is 10

この例は、クラスでの static の使用例を示しています。

// static2.cpp
// compile with: /EHsc
#include <iostream>

using namespace std;
class CMyClass {
public:
   static int m_i;
};

int CMyClass::m_i = 0;
CMyClass myObject1;
CMyClass myObject2;

int main() {
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject1.m_i = 1;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   myObject2.m_i = 2;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;

   CMyClass::m_i = 3;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
}
0
0
1
1
2
2
3
3

次の例では、メンバー関数内で宣言されたローカル変数 static を示します。 static 変数は、プログラム全体で使用できます。型のすべてのインスタンスは、static 変数の同じコピーを共有します。

// static3.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
struct C {
   void Test(int value) {
      static int var = 0;
      if (var == value)
         cout << "var == value" << endl;
      else
         cout << "var != value" << endl;

      var = value;
   }
};

int main() {
   C c1;
   C c2;
   c1.Test(100);
   c2.Test(100);
}
var != value
var == value

C++11 以降では、static ローカル変数の初期化はスレッド セーフであることが保証されています。 この機能は、静的マジックと呼ばれることがあります。 ただし、マルチスレッド アプリケーションでは、後続の割り当てはすべて同期する必要があります。 CRT の依存関係を避けるには、/Zc:threadSafeInit- フラグを使用してスレッド セーフな静的初期化の機能を無効にできます。

extern

extern として宣言されたオブジェクトと変数は、別の翻訳単位または外側のスコープで定義されているオブジェクトを、外部リンケージを持つものとして宣言します。 詳細については、extern と、翻訳単位とリンケージを参照してください。

thread_local (C++11)

thread_local 指定子で宣言された変数は、それが作成されたスレッドでのみアクセスできます。 変数は、スレッドが作成されるときに作成され、スレッドが破棄されるときに破棄されます。 各スレッドには、それ自体の変数のコピーがあります。 Windows では、thread_local は Microsoft 固有の __declspec( thread ) 属性と機能的に同等です。

thread_local float f = 42.0; // Global namespace. Not implicitly static.

struct S // cannot be applied to type definition
{
    thread_local int i; // Illegal. The member must be static.
    thread_local static char buf[10]; // OK
};

void DoSomething()
{
    // Apply thread_local to a local variable.
    // Implicitly "thread_local static S my_struct".
    thread_local S my_struct;
}

thread_local 指定子に関する注意点:

  • DLL 内の動的に初期化されたスレッド ローカル変数は、呼び出し元のすべてのスレッドで正しく初期化されない場合があります。 詳細については、threadを参照してください。

  • thread_local 指定子は、static または extern と組み合わせることができます。

  • thread_local は、データの宣言と定義にのみ適用できます。thread_local は、関数の宣言または定義では使用できません。

  • thread_local は、静的ストレージ存続期間のあるデータ項目にのみ指定できます。これには、グローバル データ オブジェクト (staticextern の両方)、ローカルな静的オブジェクト、クラスの静的データ メンバーが含まれます。 thread_local 宣言されたローカル変数は、他のストレージ クラスが指定されなき場合は暗黙的に静的です。つまり、ブロック スコープの thread_localthread_local static と同じです。

  • 宣言と定義が同じファイルと別々のファイルのどちらで発生する場合でも、スレッド ローカル オブジェクトの宣言と定義には thread_local を使用する必要があります。

  • thread_local 変数を std::launch::async と使用することはお勧めしません。 詳細については、「<future> 関数」を参照してください。

この Windows では、thread_local__declspec(thread) と機能的に同等です。ただし、*__declspec(thread)* が型定義に適用できる点および C コードでは有効である点は除きます。 thread_local は C++ 標準の一部であり、移植性がより高いため、できるだけ常にこれを使用してください。

登録

Visual Studio 2017 バージョン 15.3 以降 (/std:c++17 モード以降で使用可能): register キーワードは、サポートされているストレージ クラスではなくなりました。 その使用により、診断が発生します。 キーワードは、将来使用するために標準で引き続き予約されています。

   register int val; // warning C5033: 'register' is no longer a supported storage class

例: 自動的な初期化と静的な初期化

ローカル自動オブジェクトまたは変数が、制御フローが定義に到達するたびに初期化されます。 ローカル静的オブジェクトまたは変数が、最初に制御フローが定義に到達すると初期化されます。

オブジェクトの初期化と破棄をログに記録するクラスを定義し、I1I2、および I3 の 3 つのオブジェクトを定義する、次の例を考えます。

// initialization_of_objects.cpp
// compile with: /EHsc
#include <iostream>
#include <string.h>
using namespace std;

// Define a class that logs initializations and destructions.
class InitDemo {
public:
    InitDemo( const char *szWhat );
    ~InitDemo();

private:
    char *szObjName;
    size_t sizeofObjName;
};

// Constructor for class InitDemo
InitDemo::InitDemo( const char *szWhat ) :
    szObjName(NULL), sizeofObjName(0) {
    if ( szWhat != 0 && strlen( szWhat ) > 0 ) {
        // Allocate storage for szObjName, then copy
        // initializer szWhat into szObjName, using
        // secured CRT functions.
        sizeofObjName = strlen( szWhat ) + 1;

        szObjName = new char[ sizeofObjName ];
        strcpy_s( szObjName, sizeofObjName, szWhat );

        cout << "Initializing: " << szObjName << "\n";
    }
    else {
        szObjName = 0;
    }
}

// Destructor for InitDemo
InitDemo::~InitDemo() {
    if( szObjName != 0 ) {
        cout << "Destroying: " << szObjName << "\n";
        delete szObjName;
    }
}

// Enter main function
int main() {
    InitDemo I1( "Auto I1" ); {
        cout << "In block.\n";
        InitDemo I2( "Auto I2" );
        static InitDemo I3( "Static I3" );
    }
    cout << "Exited block.\n";
}
Initializing: Auto I1
In block.
Initializing: Auto I2
Initializing: Static I3
Destroying: Auto I2
Exited block.
Destroying: Auto I1
Destroying: Static I3

この例では、オブジェクト I1I2I3 がいつどのように初期化され、いつ破棄されるかを示します。

プログラムについていくつか注意点があります。

  • 最初に、I1I2 が定義されているブロックが制御フローによって終了されると、これらは自動的に破棄されます。

  • 次に、C++ では、ブロックの先頭でオブジェクトや変数を宣言する必要はありません。 さらに、これらのオブジェクトは、制御フローが定義に到達した場合にのみ初期化されます (I2 そして I3 、そのような定義の例です。出力には、初期化された日時が正確に表示されます。

  • 最後に、I3 などの静的ローカル変数では、プログラム実行中は値が保持されますが、プログラムが終了すると破棄されます。

関連項目

宣言と定義