【问题标题】:Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?为什么这些函数局部静态对象的破坏顺序与它们的初始化顺序相反?
【发布时间】:2015-07-16 00:24:04
【问题描述】:

我有两个函数局部静态对象,一和二。 One 的构造函数和析构函数都通过 GetTwo() 访问 Two:

#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();
}

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

它输出:

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

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

另外,我听说对于 C++11,C++ 委员会为函数局部静态变量添加了一些花哨的保证,例如线程安全。如果不是未定义,那么这种行为是这些保证的一部分吗? (这会很不错,因为它会阻止您使用 One 的析构函数使用已破坏的 Two 实例来打自己的脚。)如果 GetOne 和 GetTwo 在不同的翻译单元中,有什么保证?

编辑:

感谢到目前为止的 cmets,我现在看到一个对象仅在其构造函数返回后才被认为是构造的,而不是在它第一次进入时,所以实际上在 One 之前构造了 Two。

我还尝试阅读标准并在 C++11 标准第 6.7 节第 4 项中找到了这一点:

所有块范围变量的零初始化(8.5)静态 存储持续时间(3.7.1)或线程存储持续时间(3.7.2)是 在任何其他初始化发生之前执行。持续的 具有静态存储的块范围实体的初始化(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();
}

哪些输出:

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

它在销毁后访问 One 的数据,因此未定义的行为。但至少它是确定性的。

【问题讨论】:

  • 请注意,您调用GetTwo 之前 One 的静态实例已完全构造
  • @CaptainObvlious 这没什么问题
  • @MattMcNabb 从没说过有。
  • @Captain Obvlious 谢谢,我想我明白了。我猜该标准规定对象必须以与它们的破坏顺序相反的方式完全构造。我们在二之前开始建设一,但是在一之前完成二的建设。所以二被认为是在一之前“被构造”的。

标签: c++ c++11 static-initialization


【解决方案1】:

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

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

在您的代码中,two 是在 one 的构造函数期间构造的。因此,two 的构造函数的 完成 是在 one 的构造函数完成之前排序的。

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

【讨论】:

    【解决方案2】:

    将您的 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;
      }
    

    现在您将看到 TwoOne 之前完成构造。所以TwoOne 之前被注册为被销毁,并在之后被销毁,因为它实际上是(完全)首先构建的。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-31
      • 2021-07-12
      相关资源
      最近更新 更多