关于在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实例之前被销毁的问题?< / p>
如果代码被用作库(比如在Python中)而不是作为具有自己的main()的独立程序,答案是否会改变?
答案 0 :(得分:1)
这是&#34;安全&#34;关于不同编译单元中的静态
Objects
?
不,这不安全。
这是一个不安全的例子,因为无法保证静态数据初始化的相对顺序。虽然有一些平台特定的方法来实现这一点。
See the FAQ for techniques to work around this static initialisation fiasco。该技术基本上将静态成员隐藏在函数中,然后在首次使用时初始化。
只要添加到静态成员的对象被适当地管理,即可以保持安全,即不是静态的,并且它们不会悬挂在某处或处于某种未定义的状态,并且不会遭受其他未定义的行为。
如果代码被用作库(比如在Python中)而不是作为具有自己的main()的独立程序,答案是否会改变?
我不相信这将由标准定义,而不是实施定义。据我所知,不是,鉴于流行的实现,平台及其ABI等,答案不会改变。
答案 1 :(得分: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标准(3.6.3 / 1)关于破坏具有静态存储持续时间的对象的说法。
如果具有静态存储持续时间的对象的构造函数或动态初始化的完成先于另一个对象的顺序排序,则在第一个析构函数的启动之前对第二个析构函数的析构函数的完成进行排序。
鉴于此,上述方法对于销毁是安全的。 <{1}}仅在最后all_objects
被破坏后才会被破坏。
答案 2 :(得分: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。它们不是直接导出的,而是用于导出的函数中。为对象调用了错误的析构函数。这是一个非常糟糕的编码选择,并且已经改变了。 在多线程构建中,根据您使用对象的方式,它可能非常糟糕。你不能知道破坏的顺序,你可以尝试在破坏之后访问它。
总的来说,我认为这不是一个好习惯。它会起作用,但在更复杂的情况下,未来的变化可能会破坏事情。
答案 3 :(得分:0)
要完成@Niall的答案,虽然初始化顺序未定义,但破坏的顺序将与初始化顺序相反。
确保任何事情的唯一方法是通过创建一个全局函数,将对象作为静态局部变量(如其他答案所示)。
在这种情况下,您将确定“静态类成员之前将删除static
对象”,因为它是在“(第一次调用全局函数)之后创建的”: / p>
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.