0
点赞
收藏
分享

微信扫一扫

C++ 静态变量或成员初始化陷阱与缺陷


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








举报

相关推荐

0 条评论