程序中所有对象objects的存储期可分为下列几种:
- 自动存储。对象的存储分配与释放,在其所在代码块的开始和结束时。所有局部对象都具有该存储,除了被static, extern, or thread_local 声明
- 静态存储。对象的存储分配与释放,在程序的开始和结束时。所有在命名Namespace作用域,全局作用域都是具有静态存储,还有static, extern声明的对象。
- 线程存储。对象的存储分配与释放,在线程的开始和结束时。只有被声明为thread_local的对象具有此存储。
- 动态存储。对象的存储分配与释放,在被需要的时候,例如new, delete
具有静态存储的对象是被禁止的,除非他们可以被平凡(trivial)析构。平凡析构的意思是,析构函数什么也不做,对象没有用户定义的或者是虚析构,所有的基类,非静态成员都可以被轻易析构。
静态函数的局部变量可以被动态初始化。
动态初始化类的静态成员变量和Namespace作用域的变量是不提倡的,只有特定情况下可以。
析构建议:
一个平凡析构体意味着它的执行不需要考虑任何先后顺序,否则可能面临访问生命周期结束的对象的风险。具有静态存储的对象,主要都是在程序结束时销毁,且没有固定顺序。因此对于具有静态存储的对象,应要求其同事具有平凡析构体。基本的类型例如int, pointers都是平凡析构的。一些好的和不好的用例如下:
const int kNum = 10; // Allowed
struct X { int n; };
const X kX[] = {{1}, {2}, {3}}; // Allowed
void foo() {
static const char* const kMessages[] = {"hello", "world"}; // Allowed
}
// Allowed: constexpr guarantees trivial destructor.
constexpr std::array<int, 3> kArray = {1, 2, 3};
// bad: non-trivial destructor
// 声明为全局或静态变量,但是std::string的析构体是non-trivial
// 如果有另个静态变量依赖kFoo, 就会有问题
// 为避免此问题,可以用 const char* 替换
const std::string kFoo = "foo";
// Bad for the same reason, even though kBar is a reference (the
// rule also applies to lifetime-extended temporary objects).
const std::string& kBar = StrCat("a", "b", "c");
void bar() {
// Bad: non-trivial destructor.
static std::map<int, int> kData = {{1, 0}, {2, 0}, {3, 0}};
}
初始化建议:
在初始化时,不仅要执行建构,也需要声明初始值时如何决定的。下列问号的原因就在于,初始化声明不明确初始值是怎样的。
int n = 5; // Fine
int m = f(); // ? (Depends on f)
Foo x; // ? (Depends on Foo::Foo)
Bar y = g(); // ? (Depends on g and on Bar::Bar)
需要明确一个概念:常量初始化,初始化表达应是常量表达。如果对象通过构造函数初始化,那么构造函数需声明为constexpr。
struct Foo { constexpr Foo(int) {} };
int n = 5; // Fine, 5 is a constant expression.
Foo x(2); // Fine, 2 is a constant expression and the chosen constructor is constexpr.
Foo a[] = { Foo(1), Foo(2), Foo(3) }; // Fine
任何没有被如此标记的非局域静态存储变量都应该被假设为将被动态初始化,需要被认真审查。但动态初始化非局域静态存储变量是不推荐使用的,常被禁止。除非程序的其他初始化不依赖于该变量的初始化顺序。