【问题标题】:Can you tell if a C++ pointer has been destructed?你能判断一个 C++ 指针是否已被破坏吗?
【发布时间】:2011-11-20 15:55:49
【问题描述】:

我有一个全局单例,我的程序中的许多对象都在使用它。当程序关闭时,这会导致崩溃 - 它用于某些对象的析构函数中,在单例本身已被程序结束破坏之后。是否有一种可移植的方式来判断指针是否已调用“删除”?就我而言,delete 似乎将指针的值更改为 (void*)(-1),但我不知道这是否可移植。

【问题讨论】:

  • 不,没有,你也不应该。查找“单例反模式”和“静态初始化地狱”。
  • delete 不应更改指针的值。
  • 很可能是在调试版本中
  • 除了手动销毁程序的其余部分并让C 运行时仅销毁单例之外,您还应该重构以避免以这种方式使用单例。从析构函数调用单例似乎是错误的,如果您完全使用单例(我个人认为无需成为希特勒就可以做到这一点),请在对象的生命周期内调用其方法,而不是在最后。一个对象在被销毁时不应依赖于另一个对象的存在。
  • 如何解决破坏顺序问题请看这里:stackoverflow.com/questions/335369/…

标签: c++


【解决方案1】:

如果对象是在单独的文件中定义的,C++ 不会定义调用两个全局构造函数或析构函数的顺序。但是,如果对象是在同一个文件中定义的,则有一个顺序:文件范围对象按词法顺序(它们在文件中出现的顺序)构造并以相反的顺序销毁。

这可以与头文件依赖项结合使用,为 C++ 全局构造和销毁带来秩序,如下所示:

// header file A.h
#ifndef A_H_INCLUDED
#define A_H_INCLUDED
class A { /* ... */ };

#ifdef DEFINE_GLOBALS
A a_singleton;
#endif
#endif

// header file B.h
#ifndef B_H_INCLUDED
#define B_H_INCLUDED
#include "A.h"
class B : { A *pa_memb; /* ... */ }

#ifdef DEFINE_GLOBALS
B b_singleton;
#endif
#endif

// header file C.h
#ifndef C_H_INCLUDED
#define C_H_INCLUDED
#include "B.h"
#include "A.h"
class C : { A *pa_memb, B *pb_memb; /* ... */ }

#ifdef DEFINE_GLOBALS
C c_singleton;
#endif
#endif

然后,我们有一个名为 globals.cc 的文件,我们在其中执行此操作:

// globals.cc
#define DEFINE_GLOBALS
#include "C.h"
#include "A.h"
#include "B.h"

标题中的#include 守卫将确保global.cc 中的包含项处于正确的依赖顺序(并且可能会拾取丢失的内容:在上面的示例中,如果我们只包含"C.h",我们很好)。现在所有的全局对象都定义在一个翻译单元中,并以正确的顺序构造:使用的对象在用户之前构造,在用户之后销毁。

【讨论】:

    【解决方案2】:

    另一个选项是在调用delete 之后,将指针设置为NULL。然后你可以检查指针是否为NULL,并知道指针已被删除(或尚未初始化)。

    class MyClass
    {
      public:
        MyClass()
        {
          pPointer = new NewClass();
        }
    
        ~MyClass()
        {
          delete pPointer;
          pPointer = NULL;
        }
    
      private:
         NewClass *pPointer;
    }
    

    【讨论】:

      【解决方案3】:

      此代码将跟踪静态实例的生命周期;如果您进行堆分配,则同样有效。 当然,即使实例尚未构造,它也会在第一次调用instance() 时进行。 但是,如果您有一些复杂的全局静态析构函数网格,您应该能够使用它来确定实例是否已被破坏。

      class Thing
      {
      public:
          enum State { preConstruction, constructed, destructed };
          static const State& singletonState() { return state_;}
          static Thing& instance()
          {
              static Thing instance;
              return instance;
          }
      private:
          Thing()
          {
              state_ = constructed;
          }
          ~Thing()
          {
              state_ = destructed;
          }
          static State state_;
      };
      
      Thing::State Thing::state_ = Thing::preConstruction;
      

      【讨论】:

        【解决方案4】:

        简单回答:

        • 不要使用指针。
        • 使用经典的 Myers 单例。

        然后保证它在你的对象之后被销毁,只是确保它在你的对象之前被实例化。为了确保它在你的对象之前被实例化,只需在你的对象的构造函数中使用它。

        经验法则:如果你想在析构函数中使用单例。先在构造函数中使用。

        class A
        {
            A();
            A(A const&);
            A& operator=(A const&);
            public:
                static A& getInstance()  // return reference.
                {
                    static A instance;  // Created on first use.
                                        // So lazily created when expensive.
        
                    return instance;    // Guaranteed to be destroyed in reverse order of
                                        // creation In comparison to other static storage
                                        // duration objects. Thus if it is created before
                                        // your object It is guaranteed to be correctly
                                        // destroyed after your object.
        
                                        // If you want to guarantee it is created before your
                                        // object just call A::getInstance() in your constructor
                                        // the static member 'instance' will only be created
                                        // once and returning a reference is not expensive.
                }
                // If you are creating this from the constructor of a global
                // Then threading becomes a non issues as threads are not started before main
                // If you are using pthreads or Windows threads you potentially can start them
                // but it is undefined if they will work so again not your issue.
        };
        

        【讨论】:

          【解决方案5】:

          确定删除单例的简单方法是将单例的析构函数设为私有。然后你应该在任何试图删除它的地方得到一个编译错误。

          【讨论】:

            【解决方案6】:

            正如人们所说,不,你不能。有不同的技术可以跟踪这一点,但它们取决于平台。

            首先,您的实际问题是什么?您有一个单例,其他对象在销毁时正在使用它。如果你的单例被 C Runtime 销毁,什么会销毁其他对象?

            【讨论】:

            • C 运行时也会销毁其他对象,就在这个对象之后
            • 所以,我在这里看到了设计中的问题:C 运行时没有保证析构函数的特定顺序。检查单例是否已被破坏是一种黑客行为,而不是正确的解决方案。单例的方法是做什么的?
            【解决方案7】:

            不可能,我还以为这是一个未定义的区域。

            最好的办法是在清理完其他所有内容之前不要删除你的单例,否则在删除实例后将指针设置为 NULL 然后你可以检查它。不过,在我看来,一些重构是有序的。

            【讨论】:

              【解决方案8】:

              不,无法判断 C++ 指针指向的对象是否已被销毁。

              您可以使用智能指针,它会自动处理它。

              【讨论】:

              • 这对单例中的销毁顺序没有帮助。
              • @André Caron:是的,但除非更改语言没有什么会对此有所帮助。
              • 看起来问题出在设计上。
              • @Nicol:确实,这就是我评论的重点。这是给 OP 的,不是给 Dietrich 的。
              • 或者您可以使用静态存储持续时间对象并且销毁该对象成为非问题(因为它只会被销毁一次)。你只需要担心它被销毁后访问它。
              猜你喜欢
              • 2016-01-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-06
              • 2011-06-03
              相关资源
              最近更新 更多