如何确保Singleton不会过早被破坏?

时间:2012-07-07 07:20:30

标签: c++ memory-management singleton stack destructor

在我的项目中,我正在和大约4个单身人士一起制作Scott Meyer的方式。其中之一:

LevelRenderer& LevelRenderer::Instance()
{
    static LevelRenderer obj;
    return obj;
}

现在其中两个单身人士LevelRendererLevelSymbolTable互相交流。例如,在此方法中:

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是否容易被销毁。如果是这样,有没有办法解决这个烂摊子?

4 个答案:

答案 0 :(得分:4)

你不必担心这里的任何事情。* static关键字保证从初始化到程序退出时可用。因此,您可以在初始化之后的任何时刻调用静态变量。

此外,您有对LevelSymbolTable的引用,而不是局部变量。这就是类名后的&符号的含义。所以你可以在本地使用它,但它实际上是指“存在于其他地方的真实对象”。因此,当方法退出时,引用将超出范围,但它引用的对象不会。

*嗯,你可能不得不担心一件事。在析构函数中,您应该只清理任何内存或文件引用或其他具有此类性质的东西。我不知道你为什么要在析构函数中调用其他对象。

答案 1 :(得分:3)

定义对象之间的所有权关系。要LevelSymbolTable成为LevelRenderer的成员:

class LevelRenderer {
    LevelSymbolTable symbolTable;
public:
    static LevelRenderer& getInstance();
    ~LevelRenderer() { /* can use symbolTable here */ }
};

或创建一个包含LevelSymbolTable的单身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_onethe_other的构造函数或析构函数中处于活动状态。 (反之亦然。)

但是,您可以依赖它们在任何其他成员函数中以及在main本身中都处于活动状态。

答案 3 :(得分:1)

您在问题中提出的方案不太可能发生,因为在程序仍处于活动状态时可能正在调用Parse。只有当程序即将退出时才会调用析构函数。

在您的评论中,您指出了一个稍微不同的担忧,即全局析构函数相互依赖。如果您有全局对象使用某个全局容器注册自己,则可能会发生这种情况。您可能希望对象将自己从容器中移除,并且容器将弹出对象。

解决此问题的一种方法是允许容器获取向其注册的对象的所有权。这意味着在全局容器中注册的内容是动态分配的实例,而不是Scott Meyer的单例实例。然后,全局容器将在调用其全局析构函数时负责清理已注册的项目。