存储类
C++ 变量声明上下文中的存储类是管理对象的生存期、链接和内存位置的类型说明符。 给定对象只能有一个存储类。 除非使用 extern
、static
或 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
关键字。
在文件范围(全局和/或命名空间范围)内声明变量或函数时,
static
关键字将指定变量或函数具有内部链接。 在声明变量时,变量具有静态持续时间,并且除非您指定另一个值,否则编译器会将变量初始化为 0。在函数中声明变量时,
static
关键字将指定变量将在对该函数的调用中保持其状态。在类声明中声明数据成员时,
static
关键字将指定类的所有实例共享该成员的一个副本。 必须在文件范围内定义static
数据成员。 声明为const static
的整型数据成员可以有初始化表达式。在类声明中声明成员函数时,
static
关键字将指定类的所有实例共享该函数。 由于函数没有隐式this
指针,因此static
成员函数不能访问实例成员。 若要访问实例成员,请使用作为实例指针或引用的参数来声明函数。不能将
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
局部变量初始化是线程安全的。 此功能有时称为神奇的静态对象。 但是,在多线程应用程序中,必须同步所有后续分配。 可以通过使用 /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
说明符可以与static
或extern
合并。可以将
thread_local
仅应用于数据声明和定义;thread_local
不能用于函数声明或定义。只能在具有静态存储持续时间的数据项上指定
thread_local
,其中包括全局数据对象(static
和extern
)、局部静态对象和类的静态数据成员。 如果未提供其他存储类,则声明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
示例:自动初始化与静态初始化
当控制流到达其定义时,就会初始化本地自动对象或变量。 当控制流首次到达其定义时,将初始化本地静态对象或变量。
考虑以下示例,该示例定义一个记录对象的初始化和析构的类,然后定义三个对象(即 I1
、I2
和 I3
):
// 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
此示例演示如何以及何时初始化对象 I1
、I2
和 I3
以及何时销毁它们。
有关程序,需要注意几点:
首先,当控制流退出在其中定义
I1
和I2
的块时,二者将自动被销毁。其次,在 C++ 中,没有必要在块的开始处声明对象或变量。 此外,只有当控制流到达其定义时,才会初始化这些对象。 (
I2
和I3
就是此类定义的示例。)输出准确地显示了它们初始化的时间。最后,静态局部变量(如
I3
)在程序运行期间保留其值,但在程序终止时将被销毁。