为什么这些函数局部静态对象的销毁顺序不是它们的初始化顺序的倒数? [英] Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?

查看:171
本文介绍了为什么这些函数局部静态对象的销毁顺序不是它们的初始化顺序的倒数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个函数本地静态对象,一和二。一个构造函数和析构函数都通过GetTwo()访问两个:

  #include< iostream> 

struct One;
struct Two;

const One&得到一个();
const Two& getTwo();

struct Two {
const char * value =这是两个!
Two(){std :: cout<< 两构建体<< std :: endl; }
〜Two(){std :: cout<< Two destruct<< std :: endl; }
};

struct One {
One(){
std :: cout< 一个构建体< std :: endl;
const char * twoval = GetTwo()。value;
std :: cout<< twoval is:< twoval<< std :: endl;
}
〜One(){
std :: cout< 一个破坏< std :: endl;
const char * twoval = GetTwo()。value;
std :: cout<< twoval is:< twoval<< std :: endl;
}
};

const One& GetOne(){
static一个;
return one;
}

const Two& GetTwo(){
static两个;
return two;
}

int main(void){
GetOne();
}

我用g ++ 4.8.4编译:
g ++ -std = c ++ 11 [filename]



并输出:

 一个构造
两个构造
twoval是:这是两个!
一个破坏
twoval是:这是两个!
两个破坏

它们以相同的顺序构造和破坏!我读到在同一翻译单元中的C ++类的静态变量,破坏的顺序总是与构造的顺序的逆。但我猜不是?或者,这是未定义的行为吗?



此外,我听说对于C ++ 11,C ++委员会添加了一些花哨的保证函数本地静态变量, 。如果没有定义,那么这种行为是这些保证的一部分吗? (这将是相当不错,因为它会阻止你使用一个破坏的二的实例的One的析构器射击自己。)如果GetOne和GetTwo是在不同的翻译单位,如何保证?


感谢你的评论到目前为止,我现在看到一个对象被认为是构造函数返回后,而不是当它被认为是构造函数。首先输入,因此Two实际上是在一个之前构建的。



同时我试图阅读标准,并在C ++ 11标准的第6.7节第4项:


所有块范围变量的零初始化(8.5)具有静态
存储持续时间(3.7.1)线程存储持续时间(3.7.2)是在任何其他初始化发生之前执行的
。具有静态存储的块范围实体的常量
初始化(3.6.2)
持续时间(如果适用)在输入块的第一个
之前执行。 ... 这样的变量
在控件第一次通过其声明时初始化;
这样的变量在其
初始化完成后被视为初始化。


破坏,6.7点我们到3.6.3,它说:


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


< blockquote>

所以,如果我正在读这个权利:对于函数本地静态对象,它们的构造在运行时,根据调用的顺序顺序。而且,无论它们被定义在什么翻译单元中,它们都将以与运行时相关的顺序被破坏。



这听起来是否正确?这将使这是一个很好的解决方案静态顺序初始化fiasco。也就是说,我想你仍然可以用下面的代码在脚下射击:

  #include< iostream& 

struct One;
struct Two;

const One&得到一个();
const Two& GetTwo();
void PrintOneValue(const One& one);

struct Two {
Two(){std :: cout< 两构建体<< std :: endl; }
〜Two(){
std :: cout<< start Two destruct<< std :: endl;
PrintOneValue(GetOne());
std :: cout<< end Two destruct< std :: endl;
}
};

struct一个{
const char * value =这是一个!
One(){
std :: cout<< start One construct< std :: endl;
GetTwo();
std :: cout<< end One construct<< std :: endl;
}
〜One(){
std :: cout< 一个破坏< std :: endl;
}
};

void PrintOneValue(const One& one){
std :: cout< 一个人的价值是:< one.value<< std :: endl;
}

const One& GetOne(){
static一个;
return one;
}

const Two& GetTwo(){
static两个;
return two;
}

int main(void){
GetOne();
}

哪些输出:

  start一个构造
两个构造
end一个构造
一个破坏
start两个破坏
一个值是: !
end两个破坏

它在被破坏后访问一个数据,所以未定义的行为。但至少它是确定性的。

解决方案

C ++ 14 [basic.start.term] / p>


如果具有静态存储持续时间的对象的构造函数或动态初始化的完成在另一对象的排序之前,则完成析构函数第二个在第一个的析构函数的启动之前被排序。 [注:此定义允许并发销毁。 -end note]


在您的代码中,两个 一个。因此两个的构造函数的完成是在一个的构造函数完成之前排序的< 。



因此, one 的析构函数的完成在 two ,这说明您看到的内容。


I have two function-local static objects, One and Two. One's constructor and destructor both access Two through GetTwo():

#include <iostream>

struct One;
struct Two;

const One& GetOne();
const Two& GetTwo();

struct Two {
  const char* value = "It's two!";
  Two() { std::cout << "Two construct" << std::endl; }
  ~Two() { std::cout << "Two destruct" << std::endl; }
};

struct One {
  One() {
    std::cout << "One construct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
  }
  ~One() {
    std::cout << "One destruct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
  }
};

const One& GetOne() {
  static One one;
  return one;
}

const Two& GetTwo() {
  static Two two;
  return two;
}

int main(void) {
  GetOne();
}

I compile this with g++ 4.8.4: g++ -std=c++11 [filename]

And it outputs:

One construct
Two construct
twoval is: It's two!
One destruct
twoval is: It's two!
Two destruct

They are constructed and destructed in the same order! I read that for static variables of C++ classes in the same translation unit, the order of destruction is always the inverse of the order of construction. But I guess not? Or, is this undefined behavior?

Also, I heard that for C++11, the C++ committee added some fancy guarantees for function-local static variables, like thread safety. If not undefined, then is this behavior a part of those guarantees? (Which would be pretty nice, since it would stop you from shooting yourself in the foot with One's destructor using a destructed instance of Two.) And what's guaranteed if GetOne and GetTwo are in different translation units?

EDIT:

Thanks for the comments so far, I see now that an object is considered constructed only after its constructor returns, not when it is first entered, so Two is actually constructed before One.

Also I've attempted to read the standard and found this in the C++11 standard, section 6.7 item 4:

The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. ...such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization.

And for destruction, 6.7 points us to 3.6.3, which says:

If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.

So if I'm reading this right: For function-local static objects, their construction is "sequenced" at runtime, based on the order the functions are called. And, no matter what translation unit they are defined in, they will be destructed in the inverse of that runtime-dependent order.

Does this sound right? That would make this a nice solution to the static order initialization fiasco. That said, I think you could still shoot yourself in the foot with the code below:

#include <iostream>

struct One;
struct Two;

const One& GetOne();
const Two& GetTwo();
void PrintOneValue(const One& one);

struct Two {
  Two() { std::cout << "Two construct" << std::endl; }
  ~Two() {
    std::cout << "start Two destruct" << std::endl;
    PrintOneValue(GetOne());
    std::cout << "end Two destruct" << std::endl;
  }
};

struct One {
  const char* value = "It's one!";
  One() {
    std::cout << "start One construct" << std::endl;
    GetTwo();
    std::cout << "end One construct" << std::endl;
  }
  ~One() {
    std::cout << "One destruct" << std::endl;
  }
};

void PrintOneValue(const One& one) {
  std::cout << "One's value is: " << one.value << std::endl;
}

const One& GetOne() {
  static One one;
  return one;
}

const Two& GetTwo() {
  static Two two;
  return two;
}

int main(void) {
  GetOne();
}

Which outputs:

start One construct
Two construct
end One construct
One destruct
start Two destruct
One's value is: It's one!
end Two destruct

It accesses One's data after it is destructed, so undefined behavior. But at least it's deterministic.

解决方案

The actual Standard text in C++14 [basic.start.term] is:

If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first. [Note: This definition permits concurrent destruction. —end note ]

In your code, two is constructed during the constructor of one. Therefore the completion of constructor of two is sequenced-before the completion of constructor of one.

So the completion of the destructor of one is sequenced-before the completion of the destructor of two, which explains what you are seeing.

这篇关于为什么这些函数局部静态对象的销毁顺序不是它们的初始化顺序的倒数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆