根据C++标准,静态局部变量保证在第一次使用时被实例化。然而,如果在构造过程中访问静态局部对象会发生什么我很好奇。我认为这是未定义的行为。但在下面这种情况下,有什么最佳实践可以避免这种情况呢?
问题情况
Meyers单例模式在静态getInstance()
方法中使用静态局部变量,在第一次使用时构造对象。现在,如果构造函数(直接或间接地)再次调用getInstance()
,我们将面临静态初始化尚未完成的情况。这是一个最小的示例,说明了问题情况:
class StaticLocal {
private:
StaticLocal() {
// Indirectly calls getInstance()
parseConfig();
}
StaticLocal(const StaticLocal&) = delete;
StaticLocal &operator=(const StaticLocal &) = delete;
void parseConfig() {
int d = StaticLocal::getInstance()->getData();
}
int getData() {
return 1;
}
public:
static StaticLocal *getInstance() {
static StaticLocal inst_;
return &inst_;
}
void doIt() {};
};
int main()
{
StaticLocal::getInstance()->doIt();
return 0;
}
在VS2010中,这没有问题,但在VS2015中会出现死锁。
对于这种简单的情况,明显的解决方案是直接调用getData()
,而不再调用getInstance()
。然而,在更复杂的情况下(就像我的实际情况一样),这个解决方案是行不通的。
尝试解决
如果我们将getInstance()
方法更改为像这样工作的静态本地指针(因此放弃Meyers Singleton模式):
static StaticLocal *getInstance() {
static StaticLocal *inst_ = nullptr;
if (!inst_) inst_ = new StaticLocal;
return inst_;
}
很明显,我们会得到无限递归。在第一次调用时,inst_
是nullptr
,因此我们使用new StaticLocal
来调用构造函数。此时,inst_
仍然是nullptr
,因为只有当构造函数完成时才会被赋值。但是,构造函数将再次调用getInstance()
,在inst_
中找到一个nullptr
,因此再次调用构造函数。以此类推,无限循环。一种可能的解决方法是将构造函数的主体移动到
getInstance()
中:StaticLocal() { /* do nothing */ }
static StaticLocal *getInstance() {
static StaticLocal *inst_ = nullptr;
if (!inst_) {
inst_ = new StaticLocal;
inst_->parseConfig();
}
return inst_;
}
这将有效。但是,作为构造函数应该构建一个完整对象,我对这种情况不太满意。这种情况是否可以作为一个例外是有争议的,因为它是单例模式。然而,我不喜欢它。
更重要的是,如果类有一个非平凡的析构函数呢?
~StaticLocal() { /* Important Cleanup */ }
在上述情况下,析构函数从未被调用。我们失去了RAII,因此失去了C++的一个重要区别特性!我们处于像Java或C#一样的世界中...所以,我们可以将单例包装在某种智能指针中:
static StaticLocal *getInstance() {
static std::unique_ptr<StaticLocal> inst_;
if (!inst_) {
inst_.reset(new StaticLocal);
inst_->parseConfig();
}
return inst_.get();
}
这将在程序退出时正确调用析构函数。但是这迫使我们将析构函数设置为public。
此时,我觉得我正在做编译器的工作...
回到最初的问题
这种情况真的是未定义行为吗?还是VS2015中的编译器错误?
在不放弃完整构造函数和RAII的情况下,什么是最好的解决方案?
int d = getData();
。 - Marian SpanikparseConfig
中使用的其他类调用了getData
(和其他方法)。我可以重写代码,将StaticLocal
指针/引用传递给该类的构造函数,但那将是相当大的工作... - king_nak