【问题标题】:In C++, can a static object outlive its static member variable?在 C++ 中,静态对象可以比它的静态成员变量更长寿吗?
【发布时间】:2017-01-25 03:50:51
【问题描述】:

关于 C++ 中静态变量的销毁顺序,对于静态对象的静态成员变量的生命周期是否有任何保证?

例如,如果我有这样的事情(仅用于演示目的的疯狂简化示例):

class Object {
    static std::vector< Object * > all_objects;

public
    Object() {
        all_objects.push_back( this );
    }

    ~Object() {
        all_objects.erase(
          std::remove(all_objects.begin(), all_objects.end(), this), 
          all_objects.end());
    }
};

对于不同编译单元中的静态对象,这是否“安全”?也就是说,是否有任何保证 all_objects 成员变量将至少与任何有效的 Object 一样存在,或者是否存在 all_objects 在最后一个 Object 实例之前被销毁的问题?

如果代码被用作库(比如在 Python 中)而不是用作具有自己的 main() 的独立程序,答案是否会改变?

【问题讨论】:

    标签: c++ language-lawyer destructor


    【解决方案1】:

    这对于不同编译单元中的静态Objects 是否“安全”?

    不,不安全。

    这是一个不安全的示例,因为静态数据初始化的相对顺序无法保证。不过,有一些特定于平台的方法可以实现这一点。

    See the FAQ for techniques to work around this static initialisation fiasco。该技术基本上将静态成员隐藏在函数中,然后在首次使用时对其进行初始化。

    只要添加到静态成员的对象得到适当的管理,即不是静态的,它们不会悬空或处于某种未定义的状态并且不会遭受其他未定义的行为,它就可以变得安全。

    如果代码被用作库(比如在 Python 中)而不是用作具有自己的 main() 的独立程序,答案是否会改变?

    我不相信这将由标准定义,而不是由实现定义。据我所知,不,鉴于流行的实现、平台及其 ABI 等,答案不会改变。

    【讨论】:

      【解决方案2】:

      这对于不同编译单元中的静态对象是否“安全”?

      在初始化时是不安全的。无法保证 all_objects 将在编译单元中的 static 对象被构造时被初始化。

      我不清楚终止的顺序。我的猜测是破坏发生在构造的相反顺序。如果构造/初始化不安全,那么销毁也可能不安全。

      在初始化时使其安全的一种方法是将all_objects 包装在一个函数中。

      class Object {
          static std::vector<Object *>& get_all_objects();
      
      public
          Object() {
              get_all_objects().push_back( this );
          }
      
          ~Object() {
             std::vector<Object *>& all_objects = get_all_objects();
             all_objects.erase(
                std::remove(all_objects.begin(), all_objects.end(), this), 
                all_objects.end());
          }
      };
      
      std::vector<Object *>& Object::get_all_objects()
      {
          static std::vector<Object *> all_objects;
          return all_objects;
      }
      

      这就是 C++11 标准 (3.6.3/1) 关于销毁具有静态存储持续时间的对象的内容。

      如果具有静态存储持续时间的对象的构造函数或动态初始化的完成顺序在另一个之前,则第二个的析构函数的完成顺序在第一个的析构函数的启动之前。

      鉴于此,上述方法对于销毁是安全的。只有在最后一个 Object 被销毁后,all_objects 才会被销毁。

      【讨论】:

      • 函数级静态变量与全局(或类)级静态变量的销毁顺序规则是什么?我们确定all_objects 会一直存在直到所有静态对象消失吗?
      • “构造函数的完成”位会改变吗?例如stackoverflow.com/a/335746/3022952 表示,当您在 Object 的构造函数中调用 get_all_objects() 函数时,all_objects 变量将在第一个 Object 的构造函数完成之前完成其构造函数,这意味着 all_objects 的析构函数只会在all 对象的析构函数完成。还是我解释错了?
      【解决方案3】:

      静态变量确实具有全局范围,因为它们不在函数或方法的堆栈上。所以在最后可能的时间调用析构函数。

      所以在单线程环境中我看不出有任何问题。这是一个愚蠢的例子,但它确实运行。在 return 语句之后调用两个静态的析构函数。

      ob.h
      class ob
      {
        static int a;
        static int b;
      public:
        ob()
        {
          a++;
        }
        ~ob()
        {
          b--;
        }
      };
      

      main.cpp #include ob.h;

      int ob::a = 0;
      int ob::b = 0;
      
      void tt()
      {
        static ob zz;
      }
      
      int main() 
      {
        static ob yy;
        tt();
      
        {
          ob b;
        }
        ob a;
      
        return 1;
      }
      

      关于另一个编译单元中的静态变量,将取决于您如何使用它。例如,如果所有内容都是内联的,并且在 A.dll 和 B.dll 中使用了标头,则它们之间将没有引用,并且您必须在每个单元中初始化静态,为它们提供唯一的地址。但是,如果它位于导出它的 lib 或 dll 中,您将使用相同的内存地址。 在我们有同一个类的两个版本之前,我已经看到了一个问题。对象 A 为 1.0,对象 B 为 1.2。它们不是直接导出的,而是在导出的函数中使用。为对象调用了错误的析构函数。那是一个非常糟糕的编码选择,并且被改变了。 在多线程构建中,这可能会非常糟糕,具体取决于您使用对象的方式。你不知道销毁的顺序,你可以在销毁后尝试访问它。

      总的来说,我会说这不是一个好习惯。它会起作用,但在更复杂的情况下,未来的变化可能会很快打破局面。

      【讨论】:

        【解决方案4】:

        为了完成@Niall的回答,虽然初始化的顺序是不确定的,但销毁的顺序将是初始化顺序的倒数。

        确定任何事情的唯一方法是创建一个全局函数,将您的对象作为静态局部变量(如其他答案所示)。

        在这种情况下,您肯定会知道 static 对象将在静态类成员“之前”被删除,因为它是在“之后”创建的(第一次调用全局函数时):

        class Object {
            static std::vector< Object * > all_objects;
        
        public
            Object() {
                all_objects.push_back( this );
            }
        
            ~Object() {
                all_objects.erase(
                  std::remove(all_objects.begin(), all_objects.end(), this), 
                  all_objects.end());
            }
        };
        
        Object& static_obj()
        {
             static Object obj;
             return obj;
        }
        
        std::vector< Object * > Object::all_objects; // It is created first (before main)
        
        int main()
        {
            Object& o = static_obj(); // `obj` is initialized here.
        } // At the end of the program, `obj` will be destroid first.
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-20
          • 2013-07-02
          • 2023-03-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多