【问题标题】:How to ensure Singleton is not destroyed prematurely?如何确保 Singleton 不会过早销毁?
【发布时间】:2012-07-07 13:20:18
【问题描述】:

在我的项目中,我使用了大约 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 的实例是由堆栈分配的。因此,保证使用 last created first destroy 规则被销毁。现在,如果LevelSymbolTable 的实例LevelRenderer 的实例之后创建,它会在LevelRenderer 的实例之前被销毁,对吧?那么,如果我在LevelRenderer 中调用LevelSymbolTable 的方法(尤其是在LevelRenderer 的析构函数中),我将踏上未定义的行为领域。

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

【问题讨论】:

  • 是什么让您认为静态变量可以在程序运行时被销毁?
  • @MrLister 如果LevelRenderer 在程序退出(即在其析构函数中)调用LevelSymbolTable 并且LevelSymbolTable 已经被销毁怎么办?
  • 好吧,说真的,你为什么要这么做?我的意思是,这可能发生的唯一一次是在程序停止运行之后(即在main 中的“返回”之后)并且系统正在进行清理。你真的想在析构函数中做GenerateSpriteFromSymbol之类的事情吗?
  • 那为什么不把保存磁盘文件的代码放到LevelSymbolTable的析构函数中呢?
  • @IntermediateHacker 你不能让LevelSymbolTable 成为LevelRenderer 的成员吗?如果两个单例以这种方式相互依赖,有时定义它们之间的所有权关系可能是有意义的。

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    或者创建一个包含SymbolTableRenderer 的单例Level

    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 作为一个知道如何呈现自身并拥有其符号表的普通类。并将其生命周期与它应该在应用程序中表示的级别相关联。

    【讨论】:

      【解决方案3】:

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

      class one {
        one() {}
        ~one() {}
      };
      
      class two {
        two() {}
        ~two() {}
      };
      
      one the_one;
      two the_other;
      
      int main() {
        ...
        return 0;
      }
      

      您不能也不应该假设the_onethe_other 的构造函数或析构函数中处于活动状态。 (反之亦然。)

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

      【讨论】:

      • 不正确 - 单个编译单元中全局对象的初始化顺序已明确定义。您可以指望在 the_other 之前创建 the_one
      • 为了简单起见,我只是把它们放在一起;它们可能位于不同的 .cpp 文件中。
      • 是的,但这仍然与 OP 的情况恕我直言。他在第一次使用时创建实例,并且语言保证它们将按照创建的相反顺序被销毁
      【解决方案4】:

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

      在您的 cmets 中,您表示了一种稍微不同的担忧,即 全局析构函数相互依赖性。如果您有将自己注册到某个全局容器的全局对象,这实际上可能会发生。您可能期望对象会从容器中移除自己,并且容器会弹出对象。

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

      【讨论】:

        猜你喜欢
        • 2015-11-13
        • 2011-01-15
        • 2017-06-14
        • 2021-02-14
        • 2016-09-17
        • 2012-09-23
        • 2015-03-03
        • 1970-01-01
        相关资源
        最近更新 更多