Является ли законным инициализировать переменную thread_local в деструкторе глобальной переменной?

Эта программа:

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

Печать

Bar()
~Bar()
Foo()

для меня (GCC 6.1, Linux, x86-64). ~ Foo() никогда не вызывается. Это ожидаемое поведение?

Ответы

Ответ 1

Стандарт не охватывает этот случай; самым строгим показанием было бы то, что законно инициализировать thread_local в деструкторе объекта со статической продолжительностью хранения, но запретить программе продолжать нормальное завершение.

Проблема возникает в [basic.start.term]:

1 - Деструкторы ([class.dtor]) для инициализированных объектов (т.е. объекты, чье время жизни ([basic.life]) началось) со статической продолжительностью хранения вызываются в результате возврата из основного и в результате вызова std:: exit ([support.start.term]). Деструкторы для инициализированных объектов с длительностью хранения потока в заданном потоке вызываются в результате возврата из исходной функции этого потока и в результате этого потока, вызывающего std:: exit. По завершении деструкторов для всех инициализированных объектов с длительностью хранения потоков в этом потоке секвенируются перед началом деструктора любого объекта со статической продолжительностью хранения. [...]

Итак, завершение bar::~Bar::foo::~Foo секвенировано до начала bar::~Bar, что является противоречием.

Единственный выход может состоять в том, чтобы утверждать, что [basic.start.term]/1 применяется только к объектам, срок жизни которых начался в момент завершения программы/потока, но contra [stmt.dcl] имеет:

5 - Деструктор для объекта области блока со статикой или длительностью хранения потока будет выполняться тогда и только тогда, когда он был создан. [Примечание: [basic.start.term] описывает порядок, в котором уничтожаются объекты области области с статикой и продолжительностью хранения потоков. - конечная нота]

Это явно предназначено для применения только к нормальному потоку и завершению программы, возврату из основного или из функции потока или вызову std::exit.

Кроме того, [basic.stc.thread] имеет:

Переменная с длительностью хранения потока должна быть инициализирована до ее первого использования odr ([basic.def.odr]) и, если она построена, должна быть уничтожена при выходе потока.

"Здесь" - это инструкция для разработчика, а не для пользователя.

Обратите внимание, что нет ничего плохого в начале срока жизни thread_local с помощью деструктора, так как [basic.start.term]/2 не применяется (он ранее не был уничтожен). Вот почему я считаю, что поведение undefined происходит, когда вы разрешаете программе продолжить нормальное завершение.

Аналогичные вопросы задавались раньше, хотя и о статической и статической длительности хранения, а не о thread_local vs. static; Уничтожение объектов со статической продолжительностью храненияhttps://groups.google.com/forum/#!topic/comp.std.c++/Tunyu2IJ6w0) и Деструктор статического объекта, построенного в деструкторе другого статического объекта. Я склонен согласиться с Джеймсом Канзе по последнему вопросу о том, что [defns.undefined] применяется здесь, а поведение undefined, потому что Стандарт не определяет его. Лучшим способом продвижения для кого-то будет стоять, чтобы открыть отчет о дефекте (охватывающий все комбинации static и thread_local, инициализированные в деструкторах static и thread_local s), чтобы надеяться на окончательный ответ.