在我的项目中,我正在和大约4个单身人士一起制作Scott Meyer的方式。其中之一:
LevelRenderer& LevelRenderer::Instance()
{
static LevelRenderer obj;
return obj;
}
现在其中两个单身人士LevelRenderer
和LevelSymbolTable
互相交流。例如,在此方法中:
void LevelRenderer::Parse(std::vector<std::string>& lineSet)
{
LevelSymbolTable& table = LevelSymbolTable::Instance();
/** removed code which was irrelevant **/
// for each line in lineSet
BOOST_FOREACH(std::string line, lineSet)
{
// for each character in the line
BOOST_FOREACH(char sym, line)
{
/** code... **/
// otherwise
else
{
sf::Sprite spr;
// Used LevelSymbolTable's Instance here...
table.GenerateSpriteFromSymbol(spr, sym);
// ^ Inside LevelRenderer
/** irrelevant code... **/
}
}
}
}
现在,虽然问题尚未发生。我担心的是,如果LevelSymbolTable
实例在之前已被销毁,我会调用GenerateSpriteFromSymbol
该怎么办?
由于我使用了Scott Meyer方式,因此Singleton的实例是由堆栈分配的。因此可以保证使用最后创建的第一个被破坏的规则来销毁它。现在,如果在 LevelSymbolTable
的实例之后创建了{em> 的实例,那么它将在 LevelRenderer
的实例之前被销毁,对?那么,如果我在LevelRenderer
内调用LevelSymbolTable
的方法(特别是在LevelRenderer
的析构函数中),我将会踩到未定义的行为域。
正如我之前所说,调试时实际上并没有出现这个问题,纯粹是我的假设和猜测。那么,我的结论是否正确?在LevelRenderer
之前LevelSymbolTable
是否容易被销毁。如果是这样,有没有办法解决这个烂摊子?
答案 0 :(得分:4)
你不必担心这里的任何事情。* static关键字保证从初始化到程序退出时可用。因此,您可以在初始化之后的任何时刻调用静态变量。
此外,您有对LevelSymbolTable的引用,而不是局部变量。这就是类名后的&符号的含义。所以你可以在本地使用它,但它实际上是指“存在于其他地方的真实对象”。因此,当方法退出时,引用将超出范围,但它引用的对象不会。
*嗯,你可能不得不担心一件事。在析构函数中,您应该只清理任何内存或文件引用或其他具有此类性质的东西。我不知道你为什么要在析构函数中调用其他对象。
答案 1 :(得分:3)
定义对象之间的所有权关系。要LevelSymbolTable
成为LevelRenderer
的成员:
class LevelRenderer {
LevelSymbolTable symbolTable;
public:
static LevelRenderer& getInstance();
~LevelRenderer() { /* can use symbolTable here */ }
};
或创建一个包含Level
和SymbolTable
的单身Renderer
:
class Level {
SymbolTable symbolTable;
Renderer levelRenderer; // note the order here
public:
static Level& getInstance();
private:
/* have LeverRenderer save reference to symbol table,
now renderer can use symbol table anywhere */
Level() : levelRenderer(symbolTable)
{ /* ... */ }
};
编辑:或完全摆脱单身人士。见why singletons are bad。我不知道你的应用程序的结构,但从我看到的,你可以Level
作为一个普通的类知道如何渲染自己并有它的符号表。并将其生命周期连接到应用程序中应该表示的级别。
答案 2 :(得分:1)
静态实例将在程序开头(主要之前)创建并在结束时(主要之后)清理,并且您不能依赖于清理它们的任何特定顺序。也就是说,如果你有两个实例(为了简单起见,我们只是将它们设为全局)
class one {
one() {}
~one() {}
};
class two {
two() {}
~two() {}
};
one the_one;
two the_other;
int main() {
...
return 0;
}
您不能也不应该假设the_one
在the_other
的构造函数或析构函数中处于活动状态。 (反之亦然。)
但是,您可以依赖它们在任何其他成员函数中以及在main本身中都处于活动状态。
答案 3 :(得分:1)
您在问题中提出的方案不太可能发生,因为在程序仍处于活动状态时可能正在调用Parse
。只有当程序即将退出时才会调用析构函数。
在您的评论中,您指出了一个稍微不同的担忧,即全局析构函数相互依赖。如果您有全局对象使用某个全局容器注册自己,则可能会发生这种情况。您可能希望对象将自己从容器中移除,并且容器将弹出对象。
解决此问题的一种方法是允许容器获取向其注册的对象的所有权。这意味着在全局容器中注册的内容是动态分配的实例,而不是Scott Meyer的单例实例。然后,全局容器将在调用其全局析构函数时负责清理已注册的项目。