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;}


output:

// "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;}






0 0
原创粉丝点击