【发布时间】:2016-03-09 10:45:20
【问题描述】:
C++ 标准保证在第一次使用时实例化静态局部变量。但是,我想知道如果我在构建静态本地对象时访问它会发生什么。我假设这是UB。 但是在以下情况下避免这种情况的最佳做法是什么?
有问题的情况
Meyers Singleton 模式在第一次使用时使用静态 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();
}
这将在程序退出时正确调用析构函数。但它迫使我们公开析构函数。
在这一点上,我觉得我正在做编译器的工作......
回到原来的问题
这种情况真的是未定义的行为吗?还是VS2015的编译器bug?
这种情况的最佳解决方案是什么,最好不要删除完整的构造函数和 RAII?
【问题讨论】:
-
为什么要让你的 getInstance 函数返回一个指针? IMO 可以使用参考资料。
-
parseConfig是一个成员函数,可以写成
int d = getData();。 -
@H.Guijt 这个问题与错误的 CLR/子系统设置有关,导致在调用 main 之前崩溃。在我的问题中,这不是问题(调试、x86、控制台应用程序)
-
所以有人试图访问一个尚未构造的对象,而它正在被构造。你想要发生什么?
标签: c++ visual-studio-2015 static-initialization