C++ static global non-POD: теория и практика

Я читал документы конвенций кодирования Qt и наткнулся на следующий абзац:

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

Я знаю, что такое теория говорит, но я не понимаю часть "совсем нет". Иногда я использую не-POD глобальную статику const (e.g: QString), и мне никогда не приходило в голову, что они могут не быть инициализированы... Это относится к общим объектам / DLL? Это происходит только для некоторых компиляторов?

Что вы думаете об этом правиле?

5 ответов


часть "совсем нет" просто говорит, что стандарт C++ молчит об этой проблеме. Он не знает об общих библиотеках и, следовательно, ничего не говорит о взаимодействии некоторых функций C++ с ними.

на практике я видел глобальные статические глобалы без POD, используемые в Windows, OSX и многих версиях Linux и других Unices, как в GUI, так и в программах командной строки, как плагины и как автономные приложения. По крайней мере один проект (который использовал non-POD static globals) имели версии для полного набора всех комбинаций из них. Единственная проблема, которую я когда-либо видел, заключалась в том, что очень старая версия GCC генерировала код, который вызывал dtors таких объектов в динамических библиотеках, когда исполняемый файл останавливался, а не когда библиотека была выгружена. Конечно, это было фатально (библиотечный код был вызван, когда библиотека уже ушла), но это было почти десять лет назад.

но конечно, это еще ничего не гарантирует.


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


Если статический объект определен в объекте, на который нет ссылки, компоновщик может полностью обрезать объект, включая код статического инициализатора. Это будет делайте это регулярно для libs (вот как libc не полностью связан при использовании его частей под gnu, например).

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


Я не вижу проблем с глобальными объектами с конструкторами.

Они просто не должны зависеть от других глобальных объектов в своем конструкторе (или деструкторе).

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

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

его не так сложно придерживаться этих правил.


C++ не определяет порядок, который статические инициализаторы выполняют для объектов в разных единицах компиляции (порядок хорошо определен в единице компиляции).

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

в этом сценарии возможно, что B будет инициализирован первым и вызовет неинициализированный объект. Это может быть одна вещь, которая подразумевается под "совсем нет" - объект используется, когда у него не было возможности инициализировать его сначала (даже если он может быть инициализирован позже).

Я полагаю, что динамическое связывание может добавить сложности, о которых я не думал, что cuase объект никогда не будет инициализирован. В любом случае, суть в том, что static initializatino вводит достаточно потенциальных проблем, которых следует избегать, где это возможно, и очень тщательно обрабатывать, где вы должны использовать его.