Уничтожение объектов со статической продолжительностью хранения
Рассмотрим следующую программу.
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.