调用成员函数前,是否能保证对象已完成初始化?附示例代码
调用对象的成员函数前,是否能保证该对象已完成初始化?
答案是:不能保证,尤其是你代码里这种跨编译单元的全局对象场景,这属于C++里典型的「全局对象初始化顺序问题」。
先把你的代码整理得更清晰些:
你的示例代码
main.cpp
int main() { return 0; }
file1.cpp
#include "b.h" B b;
file2.cpp
#include "a.h" A a;
a.h
#pragma once #include<iostream> #include "b.h" extern B b; class A { public: A(){ std::cout << "a ctor" << std::endl; b.Use(); } };
b.h
#pragma once #include<iostream> class B { public: B() { std::cout << "b ctor"<<std::endl; }; void Use() { std::cout << "B::Use called" << std::endl; } };
问题根源
C++标准明确规定:不同编译单元中的全局对象,它们的初始化顺序是未指定的。这里file1.cpp里的b和file2.cpp里的a属于两个独立的编译单元,编译器和链接器完全可以自由决定先初始化哪个。
如果运气不好,a先被初始化,那么A的构造函数会直接调用b.Use()——但此时b的构造函数还没执行,b处于未完全初始化的状态。调用一个未初始化对象的成员函数属于未定义行为:可能程序直接崩溃,可能输出乱码,甚至可能看起来正常运行但埋下隐藏的bug,完全不可控。
解决方法
要规避这个问题,常用的有几种方案:
改用局部静态对象(Meyers单例模式):
把全局对象改成函数内的局部静态变量,这样第一次调用函数时才会完成初始化,而且C++11及以后标准保证这种初始化是线程安全的。修改你的代码:// b.h #pragma once #include<iostream> class B { public: B() { std::cout << "b ctor"<<std::endl; }; void Use() { std::cout << "B::Use called" << std::endl; } }; // 提供获取B实例的全局函数 B& getB() { static B b; // 第一次调用getB()时才初始化 return b; }然后修改A的构造函数:
A(){ std::cout << "a ctor" << std::endl; getB().Use(); // 此时B一定已经初始化完成 }完全避免全局对象:
把对象的创建和初始化都移到main()函数里,这样你可以完全控制初始化顺序:int main() { B b; // 先初始化B A a; // 再初始化A,此时调用b.Use()完全安全 return 0; }显式初始化函数:
给全局对象添加一个init()方法,在main()函数开头先手动调用所有必要的初始化函数,确保对象就绪后再使用。
总之,跨编译单元的全局对象初始化顺序没有任何保证,直接在一个全局对象的构造函数里调用另一个跨单元全局对象的成员函数是绝对的危险操作,一定要用上面的方法规避这种未定义行为。
内容的提问来源于stack exchange,提问作者choxsword
相关产品推荐
相关产品推荐

