2016-09-16 83 views
4

关于在C++中销毁静态变量的顺序,是否有任何有关静态对象相对于其静态成员变量的生命周期的保证?在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成员变量将至少与任何有效对象一样长,或者在最后一个对象实例之前可能会销毁all_objects

并会在被用作库中的代码的答案变化(Python中说),而不是与自己的主要一个独立的程序()?

回答

1

这将是“安全的”关于在不同的编译单元的静态对象?

这不是在初始化时的安全。不能保证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 Standard(3.6。3/1)不得不说关于具有静态存储持续时间的物体的破坏。

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

鉴于上述方法是安全的销毁。 all_objects只会在最后的Object被销毁后才会被销毁。

+0

函数级静态变量与全局(或类)级静态变量的破坏顺序规则是什么?我们确定'all_objects'会坚持到所有的静态对象消失吗? –

+0

@ R.M。,请参阅最新的答案。 –

+0

“构造函数的完成”是否改变了一些东西?例如http://stackoverflow.com/a/335746/3022952表明,当您在Object的构造函数中调用get_all_objects()函数时,all_objects变量将在完成第一个Object的构造函数之前完成其构造函数,这意味着all_objects的析构函数只会在所有*对象的析构函数完成后调用。或者我解释错了什么? –

1

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

不,它是不是安全。

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

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

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

如果代码被用作库(在Python中说)而不是作为独立的程序使用它自己的main(),那么答案会改变吗?

我不认为这将由标准定义,而不是它被实现定义。据我所知,不,考虑到流行的实现,平台和他们的ABI等,答案不会改变。

1

静态变量真的具有全局作用域,因为它们不在函数或方法的堆栈中。所以析构函数在最后一次被调用。

所以在单线程环境中,我看不到任何问题。这是一个愚蠢的例子,但它确实运行。在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。它们不是直接导出,而是用于导出的函数。为对象调用了错误的析构函数。这是一个非常糟糕的编码选择,并发生了变化。 在多线程构建中,取决于您如何使用对象,它可能非常糟糕。你无法知道破坏的顺序,你可以试图在破坏后访问某些东西。

总的来说我会说不是一个好的做法。它会工作,但在更复杂的情况下,未来的变化可能会让事情变得更快。

0

要完成@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.