Порядок уничтожения статически инициализированных, нелиберальных объектов

A недавний вопрос привлек мое внимание к тому, как constexpr изменился на С++ 14. Новая функция заключается в том, что нестационарная переменная со статической продолжительностью хранения может быть инициализирована в статической фазе инициализации, если ее инициализатор состоит из конструктора constexpr, даже если тип переменной не является литеральным типом. Точнее, новая формулировка в [basic.start.init]:

Постоянный инициализатор для объекта o является выражением, которое является константным выражением, за исключением того, что оно также может вызывать конструкторы constexpr для o и его подобъектов, даже если эти объекты имеют нелиберальные типы классов [Примечание: такой класс может иметь нетривиальный деструктор; конец примечание]. Постоянная инициализация выполняется [...], если объект со статикой или длительностью хранения потока инициализируется вызовом конструктора, и если полное выражение инициализации является постоянным инициализатором для объекта [...]

Типичным примером является std::unique_ptr, который "никогда не должен быть хуже написанного вручную":

std::unique_ptr<int> p;   // statically initialized by [unique.ptr.single.ctor],
                          // requires no code excution
int main()
{
    p = std::make_unique<int>(100);
}

// p is destroyed eventually

До этого добавления статически инициализированные переменные были либо ссылочного типа, либо типа литерала, и поэтому имели тривиальные деструкторы. Но теперь статически инициализированная глобальная переменная может иметь нетривиальный деструктор.

Как устроен такой деструкторный вызов по отношению к деструкторам динамически инициализированных глобальных объектов по отношению к другим статически инициализированным и как упорядочиваются вызовы деструктора?

Ответ 1

Рассмотрим

Если объект инициализирован статически, объект уничтожается в такой же порядок, как если бы объект был динамически инициализирован.

и

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

Теперь

Статическая инициализация должна выполняться перед любым динамическим выполняется инициализация.

Ясно, что это отвечает на первый вопрос: поскольку p гарантированно будет инициализирован до выполнения любой динамической инициализации, деструктор вызывается после уничтожения любого динамически инициализированного объекта.

В сущности, второй вопрос, то есть какой порядок имеют разрушения нескольких статически инициализированных переменных, сводится к упорядочению их инициализации:

Динамическая инициализация нелокальной переменной со статическим хранилищем длительность либо упорядоченная, либо неупорядоченная. Определения явно специализированные классы шаблонов статических данных заказаны инициализация. Другие статические элементы шаблона шаблона класса (т.е. неявно или явно инстанцированные специализации) имеют неупорядоченные инициализация. Другие нелокальные переменные со статическим хранилищем продолжительность заказали инициализацию.

Жирное предложение включает все статически инициализированные объекты, которые не являются статическими элементами данных экземпляров. Они упорядочены в пределах одной единицы перевода:

Переменные с упорядоченной инициализацией, определенные в одном единица перевода должна быть инициализирована в порядке их определения в блоке перевода.

Итак, суммируем:

  • Переменные, которые подвержены статической инициализации и не являются статическими элементами данных экземплярированного класса, уничтожаются в обратном порядке определения в файле перевода.

  • ... эти переменные всегда уничтожаются после уничтожения любого динамически инициализированного объекта.

Однако, несмотря на возможные аргументированные ошибки, ни Кланг, ни GCC, похоже, не реализуют его таким образом на данный момент: Demo.

Ответ 2

[basic.start.term]/1 (N4140) говорит:

Если объект инициализирован статически, объект уничтожается в том же порядке, как если бы объект был динамически инициализирован.

Как я понимаю, это означает, что для определения порядка уничтожения вся статическая инициализация рассматривается как динамическая (упорядоченная или неупорядоченная), а деструкторы вызываются в обратном порядке этой инициализации.