Ответ
Ключевое слово explicit применяется к конструкторам классов и запрещает компилятору использовать их для неявных преобразований типов. Оно делает вызов конструктора возможным только через явный синтаксис, предотвращая неочевидные и потенциально ошибочные преобразования.
Проблема, которую решает explicit:
Без explicit конструктор с одним параметром (или с несколькими, где все параметры, кроме одного, имеют значения по умолчанию) становится конструктором преобразования.
class StringWrapper {
std::string data;
public:
// Конструктор преобразования (неявный)
StringWrapper(const char* str) : data(str) {}
};
void printWrapper(const StringWrapper& sw) { /* ... */ }
int main() {
StringWrapper sw = "Hello"; // Неявно: const char* -> StringWrapper (вызов конструктора)
printWrapper("World"); // То же самое! Неявное преобразование аргумента.
}
Решение с explicit:
class StringWrapper {
std::string data;
public:
// Явный конструктор
explicit StringWrapper(const char* str) : data(str) {}
};
void printWrapper(const StringWrapper& sw) { /* ... */ }
int main() {
// StringWrapper sw = "Hello"; // ОШИБКА КОМПИЛЯЦИИ: неявное преобразование запрещено
StringWrapper sw("Hello"); // OK: явный вызов конструктора
StringWrapper sw2 = StringWrapper("Hello"); // OK: явное создание временного объекта
// printWrapper("World"); // ОШИБКА КОМПИЛЯЦИИ
printWrapper(StringWrapper("World")); // OK: явное преобразование
}
Когда использовать explicit:
- Практически всегда для конструкторов с одним параметром, если нет веской причины разрешать неявные преобразования.
- Это повышает безопасность и ясность кода, заставляя программиста явно указывать намеренное преобразование типа, что помогает избежать скрытых ошибок.
Ответ 18+ 🔞
Давай разжую про этот explicit, а то некоторые думают, что это какая-то магия для избранных. На самом деле, всё просто, как три копейки, но если не знать — можно на ровном месте такую дичь написать, что потом будешь сам от себя охуевать, отлаживая это.
Представь, что ты написал класс, ну, скажем, обёртку для строки. Всё вроде красиво.
class StringWrapper {
std::string data;
public:
// Конструктор, который из сишной строки делает нашу обёртку
StringWrapper(const char* str) : data(str) {}
};
И всё вроде работает, ёпта. Ты можешь делать так:
StringWrapper sw = "Hello"; // Смотри-ка, работает!
И даже передать строку в функцию, которая ждёт StringWrapper:
void printWrapper(const StringWrapper& sw) { /* ... */ }
printWrapper("World"); // И это работает! Удивление пиздец.
Вот тут-то и кроется хитрая жопа. Компилятор, такой доверчивый, видит: "Ага, у функции нужен StringWrapper, а ты передаёшь const char*. Но у StringWrapper есть конструктор, который умеет из const char* делать себя! Ща я тебе незаметно, по-тихому, создам временный объект и подсуну его".
Это называется неявное преобразование. И знаешь, в чём проблема? А в том, что доверия ебать ноль к таким автоматическим штукам. Оно может сработать там, где ты совсем не ждал. Представь класс ID — он просто хранит число. И у него конструктор от int. И ты где-то опечатался, передал не тот аргумент, а компилятор молча сделал из твоего числа ID и вызвал совсем другую перегрузку функции. И ищи потом, почему программа себя странно ведёт. Волнение ебать начинается.
Вот для этого и придумали ключевое слово explicit. Оно говорит компилятору: "Мужик, расслабься. Не надо тут ничего неявно преобразовывать. Если хочешь использовать мой конструктор — позови его явно, по имени".
Исправляем наш класс:
class StringWrapper {
std::string data;
public:
// Теперь конструктор ЯВНЫЙ
explicit StringWrapper(const char* str) : data(str) {}
};
И что теперь?
// StringWrapper sw = "Hello"; // ОШИБКА КОМПИЛЯЦИИ! Теперь так низя.
StringWrapper sw("Hello"); // А вот так — пожалуйста. Всё честно, всё на виду.
// printWrapper("World"); // Тоже ОШИБКА! Больше никаких сюрпризов.
printWrapper(StringWrapper("World")); // Правильно, сделал объект явно и передал.
Когда это воткнуть, а когда нет?
Правило простое, как палка: ставь explicit на все конструкторы с одним аргументом (или где остальные аргументы — со значениями по умолчанию), если у тебя нет железобетонной причины делать иначе.
Такая причина бывает редко. Например, твой класс по смыслу действительно является полноценной заменой того типа, из которого конструируется (как std::string для const char*). Но даже там в современном C++ часто предпочитают явность.
Итог: explicit — это не прихоть, а способ сказать "я тут осознанно пишу, а не просто так". Он убирает скрытые, неочевидные преобразования, которые могут впендюрить тебе часов отладки. Используй его, и будет тебе счастье, а не неожиданный пиздец в рантайме.