C/C++ 中静态变量初始化的问题归根节点在于一点:静态变量的使用和初始化可能位于不同的 translation unit(Effective C++),因此可能会出现使用位于初始化之前的情况,而这种情况是不可控的,可能这次编译初始化部分位于使用部分之前,而下次编译可能就相反了。
【禁止或不推荐】用函数返回值初始化静态变量
int func()
{
return 50;
}
void test()
{
static int i = func();
}
int main()
{
test();
return 0;
}
这段代码用 C 写是编译出错的,因为 C 中静态变量如果在声明时(其实也是定义时,因为如果不显式赋值,编译器会默认填 0)显式赋初值,那么值必须是在编译阶段就能确定的,也就是说只能是字面常量,也即是说即使是用另一个变量初始化它也不行。
(C99, 6.7.8p4) "All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals."
http://stackoverflow.com/questions/12720400/initializing-static-variable-with-a-function-call-gives-compilation-error
用 C++ 编写是可以通过的,但是《Google C++ Style Guide》是禁止这么干的,除非:1、函数不依赖与其他全局或者静态变量(这条可是当放宽);2、静态变量位于函数内部(因为这样代码执行顺序可控),例子见下文“静态成员为类对象部分”。
如何初始化静态成员?
.h:
class TypeTable
{
const static int type_num_ = 5;
const static char* field_type_table_[];
};
.cpp:
const char* TypeTable::field_type_table_[] = {
"int",
"long",
"flout",
"double",
"char"
};
1. 整型的(int, char 等等都属于整型) const static 成员可以在声明时初始化,例如 type_num_;
2. 其他类型的(浮点型、数组、类类型) const static 就必须在类中声明,在类外初始化,而且类外不能再带 static 关键字,如 field_type_table_。
静态成员为类对象时的问题
当静态成员为基本类型或者基本类型数组时,不存在构造与析构函数的调用,因此不存在使用时对象未被初始化的情况;当静态成员为类对象时,问题就来了,看一个例子:
test.h:
#include <iostream>
#include <string>
class Inner
{
public:
Inner(int ii): i(ii) { std::cout << "Inner: constructor" << std::endl; }
int i;
};
class Test
{
public:
Test()
{
std::cout << float_[0] << "~" << float_[1] << std::endl;
std::cout << inner_.i << std::endl;
std::cout << str_.length() << std::endl;
std::cout << i_ << std::endl;
}
static float float_[2];
static Inner inner_;
static std::string str_;
static int i_;
};
test.cpp:
#include "test.h"
static int GetValue()
{
return 1111;
}
Test test;
float Test::float_[] = { 3.1415926f, 3.1415927f };
Inner Test::inner_(100);
std::string Test::str_ = "12345";
int Test::i_ = GetValue(); // 不要用函数返回值初始化静态变量
//Test test;
main.cpp:
int main()
{
return 0;
}
output:
// "Test test;" 放在前面
3.14159~3.14159
0
0
0
Inner: constructor
// "Test test;" 放在后面
Inner: constructor
3.14159~3.14159
100
5
1111
1. 可以看出"Test test;"放在前面跟放在后面结果是不同的,放在前面编译也不会报错,只是数据被编译器在编译阶段初始化了,而没有在运行时阶段初始化。这个例子中,因为静态变量的使用和初始化位于一个 translation unit, 因此顺序是可控的,但是如果"Test test;"定义在不同的 cpp 文件中时,即当静态成员的初始化和使用位于不同的 translation unit, 二者的顺序是不明确的,所以会导致不可预料的问题出现;
2. 还有一点值得注意的是,i_ 即使是基本类型,本应该不会出现问题,但是因为它的值是通过函数调用获得的,因此也会出现问题,这就是上文“不推荐用函数返回值初始化静态变量的原因”。
这部分的参考资料:
1. Effective C++. Item 4: Make sure that objects are initialized before they’re used.
2. C++ FAQs, 2nd Edition. Section 16.14~16.18.
解决方案以 C++ FAQs 为主。
推荐阅读
1. C++ FAQs
2. Google C++ Style Guide
3. Effective C++
练习
情景:在一个类中需要保存这个类的所有【成员的名字】以及【成员编号】以供反射使用
分析:不管我调用那个构造函数,这段数据都存在,且只有一份,因此,这段数据最好是静态的。但是能不能直接定义一个 vector<pair<int, string> > 的静态成员呢?显然,这样做会诱发上文描述的初始化与使用顺序不明确的问题。提供一种解决方案,将这个“成员”放在一个静态成员函数的中,并声明为 static, 根据 C++ FAQs 中的对比,最好选择第一种方案(动态申请的方案),看代码。
test.h:
#ifndef TEST_H_
#define TEST_H_
#include <iostream>
#include <string>
#include <vector>
class Test
{
public:
typedef std::vector<std::pair<int, std::string> > FieldList;
public:
Test() {}
Test(int i, float f, std::string str) : i_(i), f_(f), str_(str) {}
~Test() {}
static const FieldList* GetFieldList();
public:
int i_;
float f_;
std::string str_;
};
#endif // TEST_H_
test.cpp:
#include "test.h"
const Test::FieldList* Test::GetFieldList()
{
static FieldList* field_list = new Test::FieldList;
field_list->push_back(std::pair<int, std::string>(1, "i_"));
field_list->push_back(std::pair<int, std::string>(2, "f_"));
field_list->push_back(std::pair<int, std::string>(3, "str_"));
return field_list;
}
main.cpp:
#include "test.h"
int main()
{
const Test::FieldList *field_list = Test::GetFieldList();
for (Test::FieldList::const_iterator it = field_list->begin();
it != field_list->end(); ++it)
{
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}