Порядок статических деструкторов
Если класс Foo
имеет статическую переменную-член Bar
, я ожидал бы, что деструктор Bar
будет запущен только после запуска последнего экземпляра Foo
destructor. Это не происходит с фрагментом кода ниже (gcc 6.3, clang 3.8):
#include <memory>
#include <iostream>
class Foo;
static std::unique_ptr<Foo> foo;
struct Bar {
Bar() {
std::cout << "Bar()" << std::endl;
}
~Bar() {
std::cout << "~Bar()" << std::endl;
}
};
struct Foo {
Foo() {
std::cout << "Foo()" << std::endl;
}
~Foo() {
std::cout << "~Foo()" << std::endl;
}
static Bar bar;
};
Bar Foo::bar;
int main(int argc, char **argv) {
foo = std::make_unique<Foo>();
}
Выходы:
Bar()
Foo()
~Bar()
~Foo()
Почему порядок разрушения здесь не наоборот?
Если ~Foo()
использует Foo::bar
, это будет полезно после удаления.
Ответы
Ответ 1
В С++ объекты строятся в порядке их возникновения и уничтожаются в обратном порядке. Сначала идет конструкция foo
, затем bar
конструкция, затем выполняется main
, тогда bar
разрушается, а затем foo
. Это поведение, которое вы видите. Переключатель появляется, потому что конструктор foo
не создает a foo
, он создает пустой unique_ptr
, поэтому вы не видите Foo()
в выводе. Затем bar
строится с выходом, а в main
вы создаете фактический foo
после того, как foo
уже долго строится.
Ответ 2
Я ожидаю, что деструктор Bar будет запущен только после того, как будет запущен последний экземпляр Foo destructor.
Нет, как элемент данных static
, Foo::bar
не зависит от всех экземпляров Foo
.
И для кода, который вы показали,
static std::unique_ptr<Foo> foo; // no Foo created here
Bar Foo::bar; // Foo::bar is initialized before main(), => "Bar()"
int main(int argc, char **argv) {
foo = std::make_unique<Foo>(); // an instance of Foo is created, => "Foo()"
}
// objects are destroyed in the reverse order how they're declared
// Foo::bar is defined after foo, so it destroyed at first => "~Bar()"
// foo is destroyed; the instance of Foo managed by it is destroyed too => "~Foo()"
Ответ 3
Усложнение заключается в том, что код не обрабатывает конструктор foo
. Случается, что foo
создается сначала, чем Foo::bar
создается. Вызов make…unique
вызывает объект foo
. Затем main
завершается, и два статических объекта уничтожаются в обратном порядке их построения: Foo::bar
уничтожается, затем foo
. Деструктор для foo
уничтожает объект foo
, на который он указывает, который создан в main
.
Ответ 4
Время жизни статических объектов основано исключительно на порядке их определения. Компилятор не "знает достаточно", когда вызывать Bar::Bar()
столько же, сколько вызывать Bar::~Bar()
.
Чтобы лучше проиллюстрировать проблему, рассмотрите этот
class Foo;
struct Bar {
Bar() {
std::cout << "Bar()" << std::endl;
}
~Bar() {
std::cout << "~Bar()" << std::endl;
}
void baz() {}
};
struct Foo {
Foo() {
bar.baz();
std::cout << "Foo()" << std::endl;
}
~Foo() {
std::cout << "~Foo()" << std::endl;
}
static Bar bar;
};
Foo foo;
Bar Foo::bar;
int main() {}
Печать
Foo()
Bar()
~Bar()
~Foo()
Добавление std::unique_ptr
откладывает Foo::Foo()
после его построения в основном, создавая иллюзию компилятора, "зная", когда вызывать Bar::Bar()
.
TL;DR Статические объекты должны быть определены позже, чем их зависимости. Прежде чем определить bar
, это так же сложно определить a std::unique_ptr<Foo>
и определить a Foo
Ответ 5
В общем, вы не должны писать код, который зависит от порядка построения или уничтожения статических (или глобальных) данных. Это делает нечитаемый и недостижимый код (вы можете предпочесть статические интеллектуальные указатели или
явная инициализация или процедуры запуска из main
). И AFAIK этот заказ не указан, когда вы связываете несколько единиц перевода.
Обратите внимание, что GCC предоставляет init_priority
и constructor
(с приоритетом). Я считаю, что вам следует избегать их использования. Тем не менее, __attribute__(constructor)
является полезным внутри плагинами для инициализации плагина.
В некоторых случаях вы также можете использовать atexit (3) (по крайней мере, в системах POSIX). Я не знаю, вызываются ли такие зарегистрированные функции до или после деструкторов (и я думаю, что вы не должны заботиться об этом порядке).