如何构建含静态成员的类的静态成员变量,消除编译顺序段错误
问题:跨编译单元静态成员的可靠初始化方案
是否存在可靠的方法编写这样的类:该类拥有一个static成员变量,而该变量所属的类本身也包含static变量?
在以下示例中,Bar类拥有Foo类型的static变量s_foo,Foo类拥有std::string类型的static常量STATIC_FOO,当bar.cpp先于foo.cpp编译时会出现段错误。请问如何调整代码结构,使其不受文件编译顺序的影响?
原代码示例
foo.h
#ifndef FOO #define FOO #include <string> class Foo { public: static const std::string STATIC_FOO; Foo(): m_foo(STATIC_FOO) {}; std::string m_foo; }; #endif
foo.cpp
#include <string> #include "foo.h" const std::string Foo::STATIC_FOO = "foo";
bar.h
#ifndef BAR #define BAR #include "foo.h" class Bar { public: static Foo s_foo; }; #endif
bar.cpp
#include "foo.h" #include "bar.h" Foo Bar::s_foo;
main.cpp
#include <iostream> #include "bar.h" int main() { Bar b; std::cout << b.s_foo.m_foo << std::endl; }
问题复现
- 当先编译
foo.cpp时,程序正常运行:
$ g++ -std=c++11 main.cpp foo.cpp bar.cpp -o main $ ./main foo
- 当先编译
bar.cpp时,触发段错误:
$ g++ -std=c++11 main.cpp bar.cpp foo.cpp -o main # bar.cpp在foo.cpp之前 $ ./main Segmentation fault (core dumped)
问题根源
这是C中跨编译单元的静态对象初始化顺序不确定导致的问题。C标准并未规定不同编译单元中全局/静态对象的初始化顺序,完全由编译器的链接顺序决定。
在这个例子中:
- 当
bar.cpp先被编译链接时,Bar::s_foo作为静态对象会优先初始化,调用Foo的构造函数 - 此时
Foo::STATIC_FOO还未完成初始化(foo.cpp的初始化逻辑尚未执行),构造函数中访问未初始化的std::string对象,直接触发段错误
解决方案:利用局部静态变量的延迟初始化
C++11及以后的标准规定:函数内的局部静态变量会在第一次调用该函数时才初始化,且初始化过程是线程安全的。我们可以把类的静态成员改成静态函数,返回局部静态变量,以此确保初始化顺序的正确性。
修改后的代码
foo.h(核心修改)
#ifndef FOO #define FOO #include <string> class Foo { public: // 将静态常量改为静态函数,返回局部静态变量 static const std::string& STATIC_FOO() { static const std::string foo_val = "foo"; return foo_val; } Foo(): m_foo(STATIC_FOO()) {}; std::string m_foo; }; #endif
foo.cpp(无需再定义STATIC_FOO)
#include "foo.h"
可选优化:修改Bar的静态成员(彻底避免初始化顺序问题)
如果要进一步消除Bar::s_foo的初始化不确定性,同样可以用局部静态变量的方式:
bar.h
#ifndef BAR #define BAR #include "foo.h" class Bar { public: static Foo& s_foo() { static Foo foo_instance; return foo_instance; } }; #endif
bar.cpp(无需再定义s_foo)
#include "bar.h"
修改后的main.cpp(如果调整了Bar的s_foo)
#include <iostream> #include "bar.h" int main() { std::cout << Bar::s_foo().m_foo << std::endl; }
效果验证
无论编译顺序如何,程序都能稳定运行:
$ g++ -std=c++11 main.cpp bar.cpp foo.cpp -o main $ ./main foo
$ g++ -std=c++11 main.cpp foo.cpp bar.cpp -o main $ ./main foo
内容的提问来源于stack exchange,提问作者Evan Smith
相关产品推荐
相关产品推荐

