在全局变量的析构函数中初始化thread_local变量是否合法?

Tav*_*nes 15 c++ multithreading language-lawyer thread-local-storage c++11

这个程序:

#include <iostream>

struct Foo {
    Foo() {
        std::cout << "Foo()\n";
    }

    ~Foo() {
        std::cout << "~Foo()\n";
    }
};

struct Bar {
    Bar() {
        std::cout << "Bar()\n";
    }

    ~Bar() {
        std::cout << "~Bar()\n";
        thread_local Foo foo;
    }
};

Bar bar;

int main() {
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

打印

Bar()
~Bar()
Foo()
Run Code Online (Sandbox Code Playgroud)

对我来说(GCC 6.1,Linux,x86-64).永远不会调用~Foo().这是预期的行为吗?

eca*_*mur 9

本标准不包括此案; 最严格的解释是,thread_local在具有静态存储持续时间的对象的析构函数中初始化a是合法的,但允许程序继续正常完成是非法的.

问题出现在[basic.start.term]中:

1 - 具有静态存储持续时间的初始化对象(即具有生命周期([basic.life])的对象的析构函数([class.dtor])被调用为从main返回并且由于调用std而导致的结果:: exit([support.start.term]).具有给定线程内的线程存储持续时间的初始化对象的析构函数被调用作为从该线程的初始函数返回的结果,并且作为该线程调用std :: exit的结果.在具有静态存储持续时间的任何对象的析构函数的启动之前,对具有该线程内的线程存储持续时间的所有初始化对象的析构函数的完成进行排序.[...]

所以完成bar::~Bar::foo::~Foo是在启动之前排序bar::~Bar,这是一个矛盾.

唯一让出可能会争辩说,[basic.start.term]/1只适用于对象,它们的生命周期已经开始在程序/线程终止点,但禁忌 [stmt.dcl]有:

5 - 当且仅当构造时,将执行具有静态或线程存储持续时间的块范围对象的析构函数.[注意:[basic.start.term]描述了破坏具有静态和线程存储持续时间的块范围对象的顺序. - 结束说明]

这显然只适用于普通线程和程序终止,通过从main或线程函数返回,或通过调用std::exit.

此外,[basic.stc.thread]具有:

具有线程存储持续时间的变量应在其第一次使用odr([basic.def.odr]之前)初始化,如果构造,则应在线程退出时销毁.

这里的"应"是对实现者的指令,而不是对用户的指令.

请注意,开始析构函数范围的生命周期没有任何问题thread_local,因为[basic.start.term]/2不适用(以前没有销毁).这就是为什么我认为当你允许程序继续正常完成时会发生未定义的行为.

之前已经提出了类似的问题,但关于静态存储持续时间而不是静态存储持续时间thread_local; 破坏具有静态存储持续时间的对象(和https://groups.google.com/forum/#!topic/comp.std.c++/Tunyu2IJ6w0),以及在另一个静态对象的析构函数内构造的静态对象的析构函数.我倾向于同意James Kanze关于[defns.undefined]适用于此的后一个问题,并且行为未定义,因为标准没有定义它.最好的办法,就是有人用站在打开的缺陷报告(涵盖的所有组合staticS和thread_local的析构函数中初始化小号staticS和thread_localS),希望一个明确的答案.