2

在我的项目中,我使用了大约 4 个按照 Scott Meyer 的方式制作的单身人士。其中之一:

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

现在是其中的两个 Singleton,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' 的 Instance 是在 ' 的 Instance 之后 LevelRenderer创建的,它会在 ' 的 Instance之前 LevelRenderer被销毁,对吧?那么,如果我调用LevelSymbolTableinside的方法LevelRenderer(尤其是在LevelRenderer's 的析构函数中),我将踏上未定义的行为领域。

正如我之前所说,这个问题在调试时实际上并没有发生,纯粹是我的假设和猜测。那么,我的结论正确吗?很LevelSymbolTable容易被销毁之前LevelRenderer。如果是这样,有没有办法摆脱这种混乱?

4

4 回答 4

4

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

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

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

于 2012-07-07T07:34:43.483 回答
3

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

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

或者创建一个Level包含SymbolTableand的单例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)
    { /* ... */ }
};

编辑:或者一起摆脱单身人士。看看为什么单身人士不好。我不知道你的应用程序的结构,但据我所知,你可以拥有Level一个普通的类,它知道如何呈现自己并拥有它的符号表。并将其生命周期连接到它应该在应用程序中表示的级别。

于 2012-07-07T08:08:56.167 回答
1

静态实例将在程序开始时(在 main 之前)创建并在最后(在 main 之后)清理,您不能依赖任何特定的清理顺序。也就是说,如果您有两个实例(为简单起见,我们将它们设为全局)

class one {
  one() {}
  ~one() {}
};

class two {
  two() {}
  ~two() {}
};

one the_one;
two the_other;

int main() {
  ...
  return 0;
}

您不能也不应该假设the_onethe_other. (反之亦然。)

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

于 2012-07-07T07:41:21.887 回答
1

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

在您的评论中,您指出了一个稍微不同的担忧,即global destructor interdependency。如果您有将自己注册到某个全局容器的全局对象,这实际上可能会发生。您可能期望对象会从容器中移除自己,并且容器会弹出对象。

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

于 2012-07-07T07:43:23.993 回答