Процесс
инициализации объекта является довольно сложным и местами запутанным.
В контексте вопроса полезно будет рассмотреть два отдельных вида инициализации: инициализацию по умолчанию и инициализацию значением.
Поведение инициализации
по умолчанию говорит что для сложных типов будет выбран подходящий конструктор, а для тривиальных типов ничего сделано не будет. Эти правила говорят о том, что обязательная процедура инициализации объекта не всегда приводит к его фактической инициализации. Для тривиальных типов инициализация является фиктивной, а созданный объект остается неинициализированным.
Тут можно обратить внимание и на то, что в поведении инициализации по умолчанию вообще ничего не говорится о вызове конструктора тривиального типа. Просто говорится о том, что при инициализации ничего не делается.
Мы часто путаем вызов конструктора и инициализацию, т.к. смысл их близок, а сильные различия не всегда вспоминаются. Фактически конструктор тривиального типа вообще не фигурирует в процессе инициализации, но бывает проще запомнить что это именно он фиктивный и ничего не делает.
Интереснее будет рассмотреть поведение инициализации
значением.
Форма
T()
может привести к вызову конструктора с пустым
std::initializer_list
, к вызову конструктора по умолчанию, а может свестись к инициализации по умолчанию в том случае, если конструктор по умолчанию удален или по своим причинам пропущен в пользовательском типе.
Каждый, кроме последних двух, пункт поведения инициализации значением оперирует словом
class
, т.е. имеет отношение только к сложным типам. Последний пункт описывает поведение инициализации для всех неописанных выше случаев как
инициализацию нулем.
И именно инициализация нулем написана в примере вопроса.
Форма
T()
хорошо подходит для инициализации
локальных временных объектов, а для инициализации
именованных объектов лучше подходит форма
T object {};
.
Форма
T object = T();
, на самом деле, выполняет инициализацию
копией, где в качестве копии используется локальный временный объект
T()
.
В результате, конкретно конструкция
T()
или
T{}
может привести к вызову конструктора по умолчанию или вызову конструктора от пустого
std::initializer_list
для сложного типа и инициализацию нулем для простого типа.
Это - очень полезное поведение в метапрограммировании, где у тебя может быть шаблон типа, в котором требуется по значению разместить объект с типом параметра шаблона. Этот объект требуется инициализировать правильным образом, но ты никак не можешь классифицировать тип, передаваемый параметром шаблона. Благодаря инициализации значением можно не задаваться вопросами природы типа из шаблонного параметра.
Но увлекаться этим подходом тоже не стоит. Я предпочту вариант
std::string string;
варианту
std::string string = std::string();
потому что для
std::string
инициализация по умолчанию сделает точно то же самое, будет эффективнее (т.к. во втором случае будет копирование) и будет понятнее.
Код
HRESULT res = HRESULT();
стоит заменить на инициализацию одним из
базовых значений. Например - кодом
S_OK
. Это создаст понимание базового состояния программы в месте определения переменной. Тип
HRESULT
имеет очень сильную семантику и сразу о многом говорит подготовленному читателю.
Только
HWND window = HWND();
или
HWND window{};
на самом деле смотрится к месту, т.к. в WinAPI нет предопределенного начального значения и объект проще провести через стандартную инициализацию значением. Уж этот краевой случай разработчики WinAPI должны предусмотреть.