Уничтожение объектов со статической продолжительностью хранения

Рассмотрим следующую программу.

struct s { ~s(); };
void f()
{
    static s a;
}

struct t { ~t() { f(); } };
int main()
{
    static s b;
    static t c;
}

Я пытаюсь выяснить, какие именно стандартные гарантии в отношении уничтожения статических объектов, но я считаю, что текст С++ 03 [basic.start.term] недостаточен.

Определено ли поведение программы? Если да, то каков порядок разрушения статических объектов a, b и c? Что произойдет, если s::~s выбрасывает исключение? Пожалуйста, объясните свои аргументы, желательно с кавычками из стандарта.

Ответы

Ответ 1

В следующем Objects относятся объекты статической продолжительности хранения.

Объекты в глобальном пространстве имен создаются до основного.

Объекты в одном модуле компиляции создаются в порядке определения.
Порядок undefined для разных единиц компиляции.

Объекты в пространстве имен создаются до того, как любая функция/переменная в этом пространстве имен будет доступна. Это может быть или не быть до основного.

Объекты в функции создаются при первом использовании.

Объекты уничтожаются в обратном порядке создания. Обратите внимание, что порядок творения определяется завершением их КОНСТРУКТОРА (не тогда, когда он был вызван). Таким образом, один объект "x", который создает другой "y" в своем конструкторе, приведет к тому, что сначала будет создан "y".

Если они не были созданы, они не будут уничтожены.

Определено ли поведение программы?

Итак, да, порядок определен правильно

b:   Created
c:   Created
c:   Destroyed Start
a:   Created (during destruction of C)
c:   Destroyed End
a:   Destroyed  (a was created after b -> destroyed before b)
b:   Destroyed

Измените код, чтобы увидеть:

#include <iostream>

struct s
{
    int mx;
    s(int x): mx(x) {std::cout <<  "S(" << mx << ")\n";}
    ~s()            {std::cout << "~S(" << mx << ")\n";}
};
void f()
{
    static s a(3);
}

struct t
{
    int mx;
    t(int x): mx(x) { std::cout << "T(" << mx << ")\n";}
    ~t()
    {                 std::cout << "START ~T(" << mx << ")\n";
                      f();
                      std::cout << "END   ~T(" << mx << ")\n";
    }
};
int main()
{
    static s b(1);
    static t c(2);
}

Выход:

$ ./a.exe
S(1)
T(2)
Start ~T(2)
S(3)
END   ~T(2)
~S(3)
~S(1)

Ответ 2

Как уже было сказано, порядок вызовов деструкторов является точным обратным порядку завершения конструкторов (3.6.3/1). Другими словами (3.8/1), остановка времени жизни объекта статической продолжительности хранения - это обратное начало жизни объекта статической продолжительности хранения. Таким образом, все сводится к тому, когда вызываются их конструкторы. Предположим, что Printer - это тип, который выводит что-то в своем конструкторе в следующих примерах.

Область пространства имен

Объекты области пространства имен (глобальные и пользовательские) в любом случае создаются до первого использования любой функции или переменной, которая определена в той же самой единицы перевода, в которой объект определен в (3.6.2/3). Эта отсроченная инициализация (после того, как main была вызвана) должна уважать порядок определения относительно других определений объектов в той же самой единице перевода этого определения объектов. (3.6.2/1).

Единица перевода 1:

void f() { }

extern Printer a;
Printer b("b");
Printer a("a");
extern Printer c;

Модуль перевода 2:

Printer c("c");
void f();

Если мы используем f, это не обязательно приведет к созданию c, так как f не определен в блоке перевода, где c был определен в. a создается после b, потому что он определен позже.

Область блока

Объекты области блока (локальная статика) создаются, когда управление проходит через их определение сначала или когда их блок сначала вводится для POD (6.7/4). Начальное время жизни снова проверяется в следующий раз, когда управление переходит через него, если создание не может успешно преуспеть (в случае исключения) (6.7/4).

void g() { static Print p("P"); }
struct A {
  A() { 
    static int n; 
    if(n++ == 0) throw "X"; 
    cout << "A"; 
    g(); 
  } 
};
void f() {
  try { static A a; } catch(char const *x) { cout << x; }
}

Этот фрагмент выводит "XAP".

Область видимости класса

Для статических членов данных то же правило применяется к порядку инициализации в соответствии с их порядком определения в пределах той же единицы перевода (3.6.2/1). Это связано с тем, что правило для этого формулируется как "объекты, определенные в области пространства имен...", а не "объекты области пространства имен...". В С++ 03 отложенная инициализация (задержка построения до тех пор, пока переменная/функция не будет использована из его единицы перевода) была разрешена только для объектов области пространства имен, которая не была предназначен. С++ 0x позволяет это также для статических членов данных ( "нелокальная переменная со статической продолжительностью хранения" ).


Таким образом, взяв приведенные выше правила и учитывая, что порядок уничтожения фактически определяется завершением конструкторов, а не их стартом, мы получим порядок ~c ~a ~b.

Если s::~s выдает исключение, С++ 0x говорит, что вызывается terminate(), и вы закончили тем, что c уничтожен и закончил время жизни a, не завершив свой деструктор, если он исключил исключение. Я не могу найти ничего в стандарте С++ 03, указав это. Кажется, это указывает только на то, что для нелокальной статики, а не для статической статистики блока, например С++ 0x.