存储类

C++ 变量声明上下文中的存储类是管理对象的生存期、链接和内存位置的类型说明符。 给定对象只能有一个存储类。 除非使用 externstaticthread_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 关键字将指定类的所有实例共享该成员的一个副本。 必须在文件范围内定义 static 数据成员。 声明为 const static 的整型数据成员可以有初始化表达式。

  4. 在类声明中声明成员函数时,static 关键字将指定类的所有实例共享该函数。 由于函数没有隐式 this 指针,因此 static 成员函数不能访问实例成员。 若要访问实例成员,请使用作为实例指针或引用的参数来声明函数。

  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

以下示例显示了成员函数中声明的局部变量 staticstatic 变量对整个程序可用;该类型的所有实例共享 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 局部变量初始化是线程安全的。 此功能有时称为神奇的静态对象。 但是,在多线程应用程序中,必须同步所有后续分配。 可以通过使用 /Zc:threadSafeInit- 标志避免对 CRT 形成依赖,来禁用线程安全的静态初始化功能。

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 说明符可以与 staticextern 合并。

  • 可以将 thread_local 仅应用于数据声明和定义;thread_local 不能用于函数声明或定义。

  • 只能在具有静态存储持续时间的数据项上指定 thread_local,其中包括全局数据对象(staticextern)、局部静态对象和类的静态数据成员。 如果未提供其他存储类,则声明 thread_local 的任何局部变量都为隐式静态;换句话说,在块范围内,thread_local 等效于 thread_local static

  • 必须为线程本地对象的声明和定义指定 thread_local,无论声明和定义是在同一文件中发生还是在单独的文件中发生。

  • 不建议将 thread_local 变量与 std::launch::async 一起使用。 有关详细信息,请参阅 <future> 函数

在 Windows 上,thread_local 在功能上等效于 __declspec(thread),但 _*__declspec(thread)* 可以应用于类型定义并且在 C 代码中有效。 请尽可能使用 thread_local,因为它是 C++ 标准的一部分,因此更易于移植。

register

Visual Studio 2017 版本 15.3 及更高版本(在 /std:c++17 模式及更高版本中可用):register 关键字不再是受支持的存储类。 使用它会导致以下诊断: 关键字仍会保留在标准中以供将来使用。

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

示例:自动初始化与静态初始化

当控制流到达其定义时,就会初始化本地自动对象或变量。 当控制流首次到达其定义时,将初始化本地静态对象或变量。

考虑以下示例,该示例定义一个记录对象的初始化和析构的类,然后定义三个对象(即 I1I2I3):

// 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++ 中,没有必要在块的开始处声明对象或变量。 此外,只有当控制流到达其定义时,才会初始化这些对象。 (I2I3 就是此类定义的示例。)输出准确地显示了它们初始化的时间。

  • 最后,静态局部变量(如 I3)在程序运行期间保留其值,但在程序终止时将被销毁。

另请参阅

声明和定义