为什么这些函数本地静态对象的破坏顺序与其初始化顺序不相反?

bbe*_*erg 6 c++ static-initialization c++11

我有两个函数本地静态对象,一个和两个.一个人的构造函数和析构函数都通过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();
}
Run Code Online (Sandbox Code Playgroud)

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

它输出:

One construct
Two construct
twoval is: It's two!
One destruct
twoval is: It's two!
Two destruct
Run Code Online (Sandbox Code Playgroud)

它们以相同的顺序构建和销毁!我读到,对于同一翻译单元中C++类的静态变量,破坏的顺序总是与构造顺序相反.但我想不是吗?或者,这是不确定的行为?

另外,我听说对于C++ 11,C++委员会为函数本地静态变量添加了一些花哨的保证,比如线程安全性.如果没有未定义,那么这种行为是否是这些保证的一部分?(这将是相当不错的,因为它会阻止你使用一个被破坏的实例2来使用One的析构函数射击自己.)如果GetOne和GetTwo在不同的翻译单元中,有什么保证?

编辑:

感谢到目前为止的评论,我现在看到一个对象被认为只在它的构造函数返回后被构造,而不是在它第一次输入时被构造,所以Two实际上是在One之前构造的.

此外,我试图阅读标准,并在C++ 11标准的第6.7项第4项中找到了这一点:

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

对于破坏,6.7指向我们3.6.3,其中说:

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

因此,如果我正确阅读:对于函数本地静态对象,它们的构造在运行时根据调用函数的顺序"排序".并且,无论它们被定义在哪个转换单元中,它们都将在与运行时相关的顺序的反转中被破坏.

这听起来不错吗?这将使这成为静态订单初始化惨败的一个很好的解决方案.也就是说,我认为你仍然可以用下面的代码射击自己:

#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();
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

start One construct
Two construct
end One construct
One destruct
start Two destruct
One's value is: It's one!
end Two destruct
Run Code Online (Sandbox Code Playgroud)

它在被破坏后访问One的数据,因此未定义的行为.但至少它是确定性的.

M.M*_*M.M 6

C++ 14 [basic.start.term]中的实际标准文本是:

如果具有静态存储持续时间的对象的构造函数或动态初始化的完成先于另一个对象的顺序排序,则在第一个的析构函数的启动之前对第二个的析构函数的完成进行排序.[注意:此定义允许同时销毁. - 尾注]

在你的代码中,two是在构造函数中构造的one.因此,完成的构造函数的two测序,之前的构造完成one.

所以析构函数one的完成是在析构函数完成之前排序的two,这解释了你所看到的.


Yak*_*ont 5

将您的ctor更改为:

  One() {
    std::cout << "Start One construct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
    std::cout << "Finish One construct" << std::endl;
  }
Run Code Online (Sandbox Code Playgroud)

现在您将看到Two施工先One完成。因此Two,要先注册然后销毁,然后再One销毁,因为它实际上是(完全)首先构造的。

Start One construct
Two construct
twoval is: It's two!
Finish One construct
One destruct
twoval is: It's two!
Two destruct
Run Code Online (Sandbox Code Playgroud)