В C++, если конструктор может быть вызван с одним аргументом, он по умолчанию считается implicit (неявным). Такой конструктор может быть использован для неявного преобразования типов. Чтобы от этого защититься, используется ключевое слово explicit.
explicit конструкторы запрещают неявные преобразования.Использование explicit позволяет избежать неожиданных преобразований, которые могут происходить, например, при передаче в функции несовпадающего по типу аргумента или при инициализации переменной.
Пример:
struct Foo { explicit Foo(int x) { /* ... */ } }; Foo a = 10; // Ошибка, explicit запрещает неявную инициализацию Foo b(10); // Ок
Если бы explicit не было, то запись Foo a = 10; была бы разрешена и могла привести к неожиданным багам.
Вопрос: Все ли конструкторы, объявленные с explicit, не могут быть вызваны при инициализации с = ?
Частый ответ: Да, explicit запрещает любую инициализацию с =.
Правильный ответ: explicit запрещает только неявные преобразования. С direct initialization (ClassName obj(param);) explicit конструктор вызывается. С копирующей инициализацией (ClassName obj = param;) — нет.
Пример:
struct A { explicit A(int) {} }; A x = 1; // Ошибка A y(1); // OK
История: В проекте был написан конструктор без explicit, позволяющий инициализацию через типы, что приводило к неожиданным автоматическим преобразованиям аргументов функций. Это вызвало появление скрытых ошибок и затруднило отладку.
История: Разработчик не поставил explicit для конструктора контейнера, и конструктор по умолчанию внезапно начал вызываться при присваивании или передаче других типов. Итог — некорректная логика создания объектов и непредсказуемое поведение.
История: Неопытный программист объявил explicit конструктор, но был удивлен, что direct initialization с круглыми скобками работает, думая, что explicit блокирует и ее. Это привело к неиспользованию удобных безопасных паттернов и лишнему коду в проекте.