C++ 静态变量或成员初始化陷阱与缺陷
来源:互联网 发布:淘宝可以说好评返现吗 编辑:程序博客网 时间:2024/06/06 01:13
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;}
// "Test test;" 放在前面3.14159~3.14159000Inner: constructor// "Test test;" 放在后面Inner: constructor3.14159~3.1415910051111
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;}
- C++ 静态变量或成员初始化陷阱与缺陷
- c陷阱与缺陷
- 《c陷阱与缺陷》
- C陷阱与缺陷
- C陷阱与缺陷
- C 陷阱与缺陷
- c陷阱与缺陷
- c陷阱与缺陷
- C陷阱与缺陷
- “c缺陷与陷阱”
- 《C陷阱与缺陷》
- c缺陷与陷阱
- C陷阱与缺陷
- c陷阱与缺陷
- c缺陷与陷阱
- C陷阱与缺陷
- C缺陷与陷阱
- c陷阱与缺陷
- lucene分词器中的Analyzer,TokenStream, Tokenizer, TokenFilter
- DIV的隐藏设置
- AVL树与Hash Table
- 文本分类实验中用java实现取名词和去除停用词
- awk中next以及getline用法示例
- C++ 静态变量或成员初始化陷阱与缺陷
- UVA 401 Palindromes 回文词
- 使用greenDAO进行开发总结
- 黑马程序员IOS键盘的关闭以及通知中心的运用
- Fedora 17 修改GRUB启动菜单顺序
- NYOJ-49 开心的小明
- Titanium笔记---ScrollableView
- 设计模式之(Decorator)装饰者模式
- poj 1681 Painter's Problem 高斯消元