Что такое explicit в C++?
Ключевое слово explicit используется для явного указания компилятору, что конструктор или оператор преобразования должны использоваться только в явных (explicit) преобразованиях типов, а не в неявных. Это помогает предотвратить неявные преобразования типов, которые могут привести к неожиданным результатам.
Явное использование конструктора:
class MyClass < public: // Объявление явного конструктора explicit MyClass(int value) : data(value) <>private: int data; >; int main() < // Ошибка компиляции - неявное преобразование от int к MyClass // MyClass obj = 42; // Явное преобразование типов MyClass obj = static_cast(42); return 0; >
В этом примере конструктор MyClass объявлен с использованием ключевого слова explicit , что предотвращает неявное преобразование от int к MyClass .
Явное использование оператора преобразования:
class Distance < public: explicit operator int() const < // Реализация оператора преобразования return static_cast(distance); > private: double distance; >; int main() < Distance d; // Ошибка компиляции - неявное преобразование от Distance к int // int result = d; // Явное преобразование типов int result = static_cast(d); return 0; >
В этом примере оператор преобразования operator int() объявлен с использованием explicit , что предотвращает неявное преобразование от Distance к int .
Дополнительные возможности:
- Предотвращение неявных преобразований: explicit используется для предотвращения неявных преобразований типов, что может уменьшить вероятность ошибок в программе.
- Явная передача параметров:Ключевое слово explicit является частью языковой поддержки для создания более явного и предсказуемого кода.
Почему использовать explicit :
- explicit следует использовать, когда необходимо избежать неявных преобразований типов, которые могут вести к непредсказуемому поведению программы.
Когда использовать explicit :
- Когда есть риск неявного преобразования типов, и такое преобразование не предоставляет ожидаемых результатов.
Использование explicit в C++ способствует ясности и безопасности кода, предотвращая неявные преобразования типов, которые могут вести к ошибкам или непредсказуемому поведению программы.
explicit — практическое применение
Принцип работы конструкторов с ключевым словом explicit понял. В чем заключается практическое применение? Если мы хотим «огородиться» от нежелательных (нам) преобразований, то:
class A < public: A(int val) < cout A(char* val) < cout >; int main()
Результат будет: «A(int)» Если мы пометим оба конструктора explicit в целях «ограждения», то при: A a(‘x’); мы все равно получим результат выше. Все равно произойдет ненужное (нам) преобразование.
Отслеживать
219k 15 15 золотых знаков 119 119 серебряных знаков 230 230 бронзовых знаков
задан 23 июл 2016 в 18:16
469 1 1 золотой знак 4 4 серебряных знака 10 10 бронзовых знаков
А что не так? Char скорее скастуется к int (и то и то целое цисло по факту) чем к указателю.
23 июл 2016 в 18:47
Иногда бывает так, что без explicit для компилятора возникает двусмысленная ситуация и код не скомпилируется.
23 июл 2016 в 19:17
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Он нужен не столько для того, чтобы вызывать конкретный конструктор, сколько для того, чтобы не было ненужного приведения типов (или приведение было однозначным).
Грубо — представим, что конструктор vector от int — не explicit . И у нас есть
void f(vector<>. );
Тогда вызов f(5) тоже будет вполне допустимой конструкцией. Вряд ли это кому-то нужно. Как и
vector v; v = 5;
Кстати, Страуструп пишет, что было бы неплохо, если бы все конструкторы по умолчанию были explicit , и явно надо было указывать отказ он него.
Отслеживать
ответ дан 23 июл 2016 в 19:21
219k 15 15 золотых знаков 119 119 серебряных знаков 230 230 бронзовых знаков
Конструктор без explicit позволяет осуществлять неявное преобразование типа аргумента в тип класса, которому принадлежит конструктор.
Если мы пометим оба конструктора explicit в целях «ограждения», то при: A a(‘x’); мы все равно получим результат выше.
Правильно, потому что запись A a(‘x’); есть direct-initialization, для которой как раз подходит explicit конструктор. Т.е. здесь есть неявное преобразование типа char в тип int для аргумента, но вот преобразование в тип A уже вполне явное.
В чем заключается практическое применение?
Неявные преобразования опасная вещь, с одной стороны они позволяют писать меньше буков кода, с другой — можно получить не совсем то, чего ожидаешь. Т.е. глядя на код видим одно, а в процессе выполнения происходит несколько иное, т.к. неявное преобразование происходит, как это ни странно, неявно.
Например, в ситуации с уже упомянутым std::vector , конструктор, принимающий целое, помечен как explicit , т.к. это целое задает размер, хотя непосвященный пользователь ожидал бы от записи std::vector v = 5; скорее помещения числа 5 в вектор, но уж точно не создания вектора из пяти элементов, инициализированных нулями. Но к счастью, explicit не дает такой записи скомпилироваться.
Дополнительно хочу отметить, что до c++11 explicit актуально было делать только конструкторы, которые можно вызывать с одним аргументом. С появлением uniform initialization это стало актуальным и для конструкторов, требующих нескольких обязательных аргументов. Например:
struct A < /* explicit */ A(int, double, char) <>>; A f() < return ; > int main() < A a = ; >
работает пока нет explicit . Если сделать конструктор явным, код придется изменить:
A f() < return A; > int main() < A a = A; // или A a; // direct initialization >
Кстати, хотя в вопросе упомянуты только конструкторы, explicit может быть применен также и к операторам преобразования (начиная с c++11 ):
struct A < explicit operator int() < return 42; >>; int main() < A a; int j = a; // (1) int i = static_cast(a); // (2) bool b = a; // (3) if(a) < // (4) // do something >else < // do another >>
Если operator int не помечен как explicit , то весь код полностью работоспособен. Когда добавляем explicit , только строка (2) остается валидной. Немного особенным в данном случае будет смотреться operator bool() вместо operator int() :
struct A < explicit operator bool() < return true; >>;
При explicit операторе будет работать только строка (4) , т.к. использование переменной в условных выражениях ( if , while , ?: ) интерпретируется как явное преобразование к bool . Без explicit код по-прежнему полностью работоспособен.
Заданные пользователем преобразования типов (C++)
Преобразование создает новое значение определенного типа из значения другого типа. Стандартные преобразования встроены в язык C++ и поддерживают встроенные типы, и вы можете создавать пользовательские преобразования для выполнения преобразований в определяемые пользователем типы или между ними.
Стандартные преобразования выполняют преобразование между встроенными типами, между указателями или ссылками на типы, связанные наследованием, в и из указателей void и в пустой указатель. Дополнительные сведения см. в разделе «Стандартные преобразования». Пользовательские преобразования выполняют преобразование между пользовательскими типами или между пользовательскими и встроенными типами. Их можно реализовать как конструкторы преобразования или как функции преобразования.
Преобразования могут быть явными, когда программист вызывает преобразование одного типа в другой (как в приведении или прямой инициализации) или неявными, когда язык или программа вызывают типы, которые отличаются от заданных программистом.
Попытка неявного преобразования выполняется, когда
- тип аргумента, предоставленного для функции, не совпадает с соответствующим параметром;
- тип значения, возвращаемого функцией, не совпадает с типом возвращаемого значения функции;
- тип выражения инициализатора не совпадает с типом инициализируемого объекта;
- тип результата выражения, которое управляет условным оператором, циклической конструкцией или параметром, не совпадает с тем, который требуется для управления;
- тип операнда, предоставленного для оператора, не совпадает с соответствующим параметром операнда. Для встроенных операторов тип обоих операндов должен совпадать; он преобразуется в общий тип, который может представлять оба операнда. Дополнительные сведения см. в разделе «Стандартные преобразования». Для пользовательских операторов тип каждого операнда должен совпадать с соответствующим параметром операнда.
Если не удается выполнить неявное преобразование с помощью стандартного преобразования, компилятор может использовать пользовательское преобразование, за которым (при необходимости) будет следовать дополнительное стандартное преобразование.
Если на сайте преобразования есть два и более пользовательских преобразования, выполняющих одно преобразование, преобразование называется неоднозначным. Неоднозначность подразумевает ошибку, так как компилятор не может определить, какое из доступных преобразований выбрать. Тем не менее, не будет ошибкой определить несколько способов выполнения одного преобразования, так как набор доступных преобразований может отличаться в разных участках исходного кода, например в зависимости от того, какие файлы заголовков входят в исходный файл. Пока на сайте преобразования доступно только одно преобразование, о неоднозначности речь не идет. Существует несколько путей возникновения неоднозначных преобразований, однако самые распространенные перечислены ниже.
- Множественное наследование. Преобразование определено в нескольких базовых классах.
- Вызов неоднозначной функции. Преобразование определено как конструктор преобразования типа целевого объекта и как функция преобразования типа источника. Дополнительные сведения см. в разделе «Функции преобразования».
Неоднозначность, как правило, можно устранить, просто более полно указав имя соответствующего типа или выполнив явное приведение для пояснения намерения.
Конструкторы преобразования и функции преобразования подчиняются правилам управления доступом членов, однако доступность преобразований учитывается, только если можно определить неоднозначное преобразование. Это означает, что преобразование может быть неоднозначным, даже если уровень доступа конкурирующего преобразования будет блокировать его использование. Дополнительные сведения о специальных возможностях членов см. в разделе «Члены контроль доступа».
Ключевое слово explicit и проблемы с неявным преобразованием
По умолчанию при создании пользовательского преобразования компилятор может использовать его для выполнения неявных преобразований. Иногда это совпадает с вашими намерениями, но в других случаях простые правила, которые определяют выполнение неявных преобразований компилятором, могут привести к тому, что он примет нежелательный код.
Одним из известных примеров неявного преобразования, которое может вызвать проблемы, является преобразование в bool . Существует множество причин, по которым может потребоваться создать тип класса, который можно использовать в логическом контексте, например для управления if оператором или циклом, но когда компилятор выполняет пользовательское преобразование в встроенный тип, компилятор может применить дополнительное стандартное преобразование после этого. Цель этого дополнительного стандартного преобразования заключается в том, чтобы разрешить такие вещи, как повышение short int от, но он также открывает дверь для менее очевидных преобразований, например из bool int , в который можно использовать тип класса в целых контекстах, которые никогда не предназначены. Эта конкретная проблема называется Сейф логическое решение. Эта проблема заключается в том, что explicit ключевое слово может помочь.
Ключевое слово explicit сообщает компилятору, что указанное преобразование нельзя использовать для выполнения неявных преобразований. Если вы хотели, чтобы синтаксическое удобство неявных преобразований перед explicit ключевое слово было введено, необходимо либо принять непредвиденные последствия, которые неявное преобразование иногда создавало или использовало менее удобные, именованные функции преобразования в качестве обходного решения. Теперь, используя explicit ключевое слово, можно создать удобные преобразования, которые можно использовать только для выполнения явных приведения или прямой инициализации, и это не приведет к таким проблемам, как это было показано в Сейф bool Problem.
Ключевое слово explicit можно применять к конструкторам преобразования с C++98, а также к функциям преобразования с C++11. В следующих разделах содержатся дополнительные сведения об использовании explicit ключевое слово.
Конструкторы преобразования
Конструкторы преобразования определяют преобразование из пользовательских или встроенных типов в пользовательские типы. В следующем примере показан конструктор преобразования, который преобразуется из встроенного типа double в определяемый пользователем тип Money .
#include class Money < public: Money() : amount< 0.0 ><>; Money(double _amount) : amount < _amount ><>; double amount; >; void display_balance(const Money balance) < std::cout int main(int argc, char* argv[]) < Money payable< 79.99 >; display_balance(payable); display_balance(49.95); display_balance(9.99f); return 0; >
Обратите внимание, что первый вызов функции display_balance , которая принимает аргументы типа Money , не требует преобразования, так как аргумент принадлежит к правильному типу. Однако во втором вызове display_balance требуется преобразование, так как тип аргумента с значением 49.95 , не то, double что ожидает функция. Функция не может использовать это значение напрямую, но так как имеется преобразование из типа аргумента ( double в тип соответствующего параметра) Money — временное значение типа Money создается из аргумента и используется для завершения вызова функции. В третьем вызове display_balance обратите внимание, что аргумент не double является аргументом, но вместо float него имеет значение 9.99 , и тем не менее вызов функции по-прежнему может быть завершен, так как компилятор может выполнить стандартное преобразование ( в данном случае — из double float ), а затем выполнить определяемое пользователем преобразование для Money завершения необходимого преобразования double .
Объявление конструкторов преобразования
Следующие правила применяются к объявлению конструктора преобразования.
- Целевым типом преобразования является сконструированный пользовательский тип.
- Конструкторы преобразований, как правило, принимают только один аргумент типа источника. Однако конструктор преобразования может указывать дополнительные параметры, если у каждого из них есть значение по умолчанию. Тип источника остается типом первого параметра.
- Конструкторы преобразований, как и все конструкторы, не указывают тип возвращаемого значения. Указание типа возвращаемого значения в объявлении является ошибкой.
- Конструкторы преобразования могут быть явными.
Явные конструкторы преобразования
Объявляя конструктор explicit преобразования, его можно использовать только для выполнения прямой инициализации объекта или явного приведения. Это не дает функциям, которые принимают аргумент типа класса, также неявно принимать аргументы типа источника конструктора преобразования, а также блокирует инициализацию копирования типа класса из значения типа источника. В следующем примере демонстрируется определение явного конструктора преобразования и влияние на правильный синтаксис кода.
#include class Money < public: Money() : amount< 0.0 ><>; explicit Money(double _amount) : amount < _amount ><>; double amount; >; void display_balance(const Money balance) < std::cout int main(int argc, char* argv[]) < Money payable< 79.99 >; // Legal: direct initialization is explicit. display_balance(payable); // Legal: no conversion required display_balance(49.95); // Error: no suitable conversion exists to convert from double to Money. display_balance((Money)9.99f); // Legal: explicit cast to Money return 0; >
В этом примере обратите внимание, что явный конструктор преобразования можно использовать для выполнения прямой инициализации типа payable . Если же вы попытаетесь выполнить инициализацию копирования Money payable = 79.99; , это приведет к ошибке. Первый вызов display_balance не включает преобразование, так как указан аргумент правильного типа. Второй вызов display_balance является ошибкой, так как конструктор преобразования нельзя использовать для выполнения неявного преобразования. Третий вызов display_balance является законным из-за явного приведения к, но обратите внимание, что компилятор по-прежнему помог завершить приведение путем вставки неявного приведения Money из float double .
Несмотря на то, что использование неявных преобразований кажется удобным, в результате могут возникать трудновыявляемые ошибки. Как показывает опыт, лучше всего объявлять все конструкторы преобразований явными за исключением тех случаев, когда необходимо, чтобы определенное преобразование выполнялось неявно.
Функции преобразования
Функции преобразования определяют преобразования из пользовательского в другие типы. Эти функции иногда называют «операторами приведения», так как они, наряду с конструкторами преобразования, вызываются, когда значение приводится к другому типу. В следующем примере показана функция преобразования, которая преобразуется из определяемого пользователем типа, Money в встроенный тип: double
#include class Money < public: Money() : amount< 0.0 ><>; Money(double _amount) : amount < _amount ><>; operator double() const < return amount; >private: double amount; >; void display_balance(const Money balance)
Обратите внимание, что переменная-член amount является закрытой, и что общедоступная функция преобразования в тип double вводится только для возврата значения amount . В функции display_balance неявное преобразование возникает, когда значение balance направляется в стандартный вывод с помощью оператора вставки в поток double компилятор может использовать функцию преобразования для Money double удовлетворения оператора потоковой вставки.
Функции преобразования наследуются производными классами. Функции преобразования в производном классе переопределяют наследуемую функцию преобразования, только когда выполняют преобразование в точно такой же тип. Например, определяемая пользователем функция преобразования производного оператора класса int не переопределяет (или даже влияет) определяемой пользователем функцией преобразования оператора базового класса коротким, даже если стандартные преобразования определяют связь преобразования между int и short .
Объявление функций преобразования
Следующие правила применяются к объявлению функции преобразования.
-
Целевой тип преобразования должен быть объявлен до объявления функции преобразования. Классы, структуры, перечисления и определения типа нельзя объявлять в объявлении функции преобразования.
operator struct String < char string_storage; >() // illegal
Явные функции преобразования
Если функция преобразования объявлена как явная, ее можно использовать только для выполнения явного приведения. Это не дает функциям, которые принимают аргумент типа целевого объекта функции преобразования, также неявно принимать аргументы типа класса, а также блокирует инициализацию копирования экземпляров типа целевого объекта из значения типа класса. В следующем примере демонстрируется определение явной функции преобразования и влияние на правильный синтаксис кода.
#include class Money < public: Money() : amount< 0.0 ><>; Money(double _amount) : amount < _amount ><>; explicit operator double() const < return amount; >private: double amount; >; void display_balance(const Money balance)
Здесь оператор функции преобразования двойной был сделан явным, и явный приведение к типу double был введен в функцию display_balance для выполнения преобразования. Если пропустить это преобразование, компилятор не сможет найти подходящий оператор вставки в поток
Explicit c что это
Кратко: ключевое слово explicit, примененное перед конструктором, делает так, что такой конструктор не может использоваться для неявных преобразованией типов. Подробности далее.
К лючевое слово explicit
К лючевое слово explicit используется для создания явных конструкторов. Другое название — «неконвертирующиеся конструкторы» (nonconverting constructors).
Рассмотрим следующий класс:
Объекты этого класса могут быть объявлены двумя способами:
MyClass ob2 = 10;
В данном случае инструкция:
MyClass ob2 = 10;
автоматически конвертируется в следующую форму:
Однако, если объявить конструктор MyClass с ключевым словом explicit, то это автоматическое конвертирование не будет выполняться. Ниже объявление класса MyClass показано с использованием ключевого слова explicit при объявлении конструктора:
explicit MyClass(int j)
Теперь допустимыми являются только конструкции следующего вида:
Вот и все объяснение. Но есть и нюансы (подводные камни) о которых необходимо помнить. Вот выдержка из Википедии:
Операторы явного преобразования
Стандарт C++ предлагает ключевое слово explicit как модификатор конструкторов с одним параметром , чтобы такие конструкторы не функционировали как конструкторы неявного преобразования. Однако это никак не влияет на действительные операторы преобразования. Например, класс умного указателя может содержать operator bool() для имитации обычного указателя. Такой оператор можно вызвать, например, так: if(smart_ptr_variable) (ветка выполняется, если указатель ненулевой). Проблемой является то, что такой оператор не защищает от других непредвиденных преобразований. Поскольку в C++ тип bool объявлен как арифметический, возможно неявное преобразование в любой целочисленный тип или даже в тип числа с плавающей точкой, что в свою очередь может привести к непредвиденным математическим операциям.
В C++11 ключевое слово explicit применимо и к операторам преобразования. По аналогии с конструкторами, оно защищает от непредвиденных неявных преобразований. Однако ситуации, когда язык по контексту ожидает булевый тип (например, в условных выражениях, циклах и операндах логических операторов), считаются явными преобразованиями и оператор явного преобразования в bool вызывается непосредственно.
- Стандарт языка программирования Си++. Где его найти?
- Как сделать массив объектов, у которых конструктор имеет аргументы
- Борьба с ошибкой линковки «In function . undefined reference to» при сборке с заранее скомпиленными библиотеками
- Создание объектов в цикле
- Про dynamic_cast в сравнении с Pascal
- Проверка кода: какие статические анализаторы существуют?
- Про нужность виртуального деструктора
- Как срабатывают конструкторы, деструкторы и инициализация переменных в C++
- Многомерные динамические массивы
- Как работать с указателями на объект
- Синтаксис объявления указателей на функции в С++ — как построить тип указателя на функцию
- Указатели на функцию в языке Си/Си++
- Как получить адрес функции в языке C++
- Массивы указателей на функцию в языке Си/Си++
- Указатель в языке C++, хорошее объяснение
- Как сделать FastCGI сервер на C/C++
- Концепция объектно-ориентированной парадигмы предельно проста.
- Небольшой логгер стека вызовов для C++
- Руководство новичка по эксплуатации компоновщика в C/C++
- Как препроцессор узнает полный путь к заголовочному файлу библиотеки в GCC
- Лямбда-функции в C++ (стандарт С++11)
- Лямбда-выражения в C++0x (С++11)
- Синтаксис лямбда выражения с языке C++
- Что обозначает декларация const в описании метода
- C++: пример использования const-указателя
- C++: учебные материалы по контейнерным классам и алгоритмам
- Интервью с Бьерном Страуструпом о языке C++
- C++: Ключевое слово explicit. Явные конструкторы
- Модификаторы public, private и protected в C++
- Права доступа при наследовании в C++
- Конструктор копирования в C++: объяснение и пример использования
- Константные методы и константные указатели в C++, краткие примеры
- Книга «Введение в язык Си++», третье издание
- Дружественные функции (методы) в С++
- Дружественные классы в С++
- Еще одна попытка объяснить, что такое ссылка и указатель, и чем они отличаются
- Что такое explicitly shared объекты и implicitly shared и чем они отличаются (краткое объяснение)
- Приведение типов в C++. Терминология, динамический полиморфизм
- Приведение типов в C++
- Указатели, ссылки и массивы в C и C++: точки над i tutorial (подробное и простое объяснение)
- Отличие ссылок от указателей в языке C++
- Как в C++ различать «константный указатель» и «указатель на константу»
- Подходы к разработке встраиваемого ПО на C++: Шаблоны
- Пример перебора std::unordered_map на C++
- Пример передачи #define определений через флаги компилятора в GCC
- Что такое lvalue и rvalue в языке C++
- Что такое l-value и r-value в С++. Простое и короткое объяснение
- Почему строковый литерал в C++ является lvalue?
- Значения Lvalue и Rvalue
- Вопросы и ответы на RSDN. Что это такое lvalue и rvalue?
- Понимание преинкремента и постинкремента в языке C++
- Приоритет операций в языке C++
- Онлайн компиляторы C++
- Как в C++ можно держать основные настройки программы и прочие конфигурирующие данные не в глобальной области видимости
- Порядок инициализации в конструкторах
- Как научиться понимать синтаксис языка C++
- Возврат значений по ссылке, по адресу (указателю) и по значению в C++
- Понимание наследования и механизма виртуальных методов в C++
- Особенности модификатора Const в C++ при работе с указателями и ссылками
- Глава книги «C++. Практика многопоточного программирования» — Разработка конкурентного кода
- Как сделать константное свойство в классе в языке C++
- Примеры работы с байтами через union и через memcpy
- Особенности языка C и C-style кода
- Как скомпилировать C++ программу с минимальным размером бинарника в GCC
- Понимание синтаксиса объявления указателей и массивов
- Техника поиска кода, использующего конструктор копирования или оператор копирования в языке Си++
- Рабочий пример простого парсера математических выражений
- Еще один пример простого парсера математических выражений, с поддержкой именованных констант
- Как узнать, какие директории будут по-умолчанию включены в INCLUDE path при компиляции C/C++ программ в GCC
- Кратко: что делают модификаторы override и final
- Переменные в единицах трансляции: размещение объектов в памяти и время жизни объектов
- Создание шаблонной фабрики объектов в языке C++
- Как бороться с ошибкой компиляции undefined reference to `std::cout’
- Как получить тип переменной в языке C++ в виде строки
- Размеры базовых математических типов int, long в различных ОС в зависимости от битности
- Пример использования constexpr и operator для задания величин с размерностью
- Ключевое слово new, оператор new и их различные формы: стандартный, размещающий, и другие
- Многомерные массивы C++ в динамической памяти через new и delete
- Перегрузка префиксного и постфиксного оператора в C++: фиктивный параметр как самое странное решение разработчиков языка