Конструктор с параметрами. Конвертирующий конструктор и ключевое слово explicit

В С++ есть так называемый конвертирующий конструктор. Это такой конструктор, который преобразует входящие в себя типы в типы своего класса:

  • Конструктор, объявленный без спецификатора-функции explicit, задает преобразование типов его параметров к типу своего класса. Такой конструктор называется конструктором преобразования (converting constructor).
  • До принятия стандарта С++11 преобразование посредством конструктора задавалось с использованием конструктора с одним параметром.
  • Со времени принятия стандарта С++11 количество параметров конвертирующего конструктора не ограничивается.
  • Конструктор преобразования ведёт себя как функция, автоматически преобразующая типы, входящие в себя, к своему родному типу.
  • Конструктор преобразования отличается от обычного конструктора с параметрами тем, что провоцирует неявные приведения типов входящих в себя параметров к типу МойКласс.
Неявные преобразования иногда удобны, а иногда вредны. Новичкам обычно сложно быстро привыкнуть к чудесам из-за неявных преобразований. В С++ даже два полностью идентичных класса компилятором считаются неодинаковыми типами. Из-за этого нельзя делать вот так:

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

Мы научили класс принимать в себя чужеродный класс так, как будто бы принимаемый класс был принимающему классу родным. Но этого мало, надо научить принимающий класс переносить в себя нужные данные от принимаемой стороны. Немного разовьём классы, чтобы они умели делать что-то полезное, и научим класс X принимать в себя данное из Y:

Тут ничего сложного нет, ключи к пониманию — знание процесса передачи значения транзитом через конструктор в класс, общее понимание методов класса и параметров функций.
На самом деле пример можно было упростить, но я решил использовать именно такой, а не более простой, потому что более простой вариант, как я могу догадываться, ясности даст меньше. Выглядит он так:

В листинге #a4 происходит то же, что и было показано ранее. Несмотря на несответствие типов присваивание работает так, как будто бы типы были одинаковые. Происходит это из-за неявного приведения, получаемого в результате работы конвертирующего конструктора. Как вы можете видеть, для параметра конвертирующего конструктора имеет значение тип принимаемой стороны. В начале типом был наш собственный класс, а в этом коде типом был int. Этот тип — это тип от присваиваемого класс объекта значения. Программисту нужно только научить принимающий класс принимать данные из чужого класса.
В некоторых ситуациях неявное приведение нежелательно.

  • Если мы явно не хотим возможности присвоения в объект объектов любого типа, отличного от типа самого объекта, если мы вообще не думаем о том, чтобы присваивать в объект объекты любого другого типа, нужно отключать конвертирующий конструктор, получить просто конструктор с параметрами. Это касается не только операции присвоения, но и вообще любой операции, которая будет доступна объекту класса (сложение, вычитание, умножение…).
  • Отключается конвертирующий конструктор с помощью ключевого слова explicit.
  • Отключение конвертирующего конструктора даёт программисту возможность большего контроля над ходом работы программы.
Иногда неявные преобразования приносят пользу, но иногда могут привести к незаметным, но серьёзным ошибкам в коде. Как правило, неявных преобразований типов, порождаемых конструктором конвертирования, как раз избегают.
Немного сложно смоделировать наглядную иллюстрацию возможного возникновения проблемы. Вспомним, как начинали изучать С++, где неявные приведения буквально удивляли нас:
Иногда это действительно могло быть полезно, но обычно приводило к неприятностям. То же самое и с неявными приведениями, проводимыми посредством конвертирующего конструктора. Оно вроде бы и удобно, но может приводить к неправильной работе программы, а поймать такую ошибку в случае с самописными классами сложнее, чем в примитивном варианте по примеру int и double. В общем, пока не понимаем как происходят приведения, неявные приведения легко оказываются занозой: любой инструмент нужно уметь использовать.
Отключаем конструктор конвертирования:.

Благодаря отключению конструктора автоматического приведения типа поведение программы больше предсказуемо, теперь программисту легче контроллировать процесс работы, потому что неявности сомнительного характера больше нет.
Иногда удобно использовать конструктор автоматических преобразований, но сохранять при этом полный контроль над ходом работы программы. Этого можно добиться при помощи вспомогательных функций. Возможно делать так:

Статическая функция в листинге #a6 нужна, чтобы не создавать объекта класса для вызова той функции. Эта функция выполняет роль посредника между конструктором конвертирования, сокрытым в секции private и уходящим в конструктор конвектирования параметром. Это как с обычными конструкторами: если что-то сокрыто в секции private, то нужен посредник для взаимодействия этого чего-то со внешним для класса миром. Но этот код имеет и недостаток, ведь теперь мы не сможем использовать конструктор при объявлении, используя круглые скобки наиболее привычным образом:



Ошибка, потому что конструктор, который мы инициируем, закрыт от прямого доступа в секции private. На самом деле это обходится перегрузкой операции, но перегрузка операций выходит за рамки этой статьи. Просто имейте в виду, что есть вот такой вариант, который является некоторым компромиссом между использованием explicit и отказа от explicit.
Конвертирующий конструктор способен зарождать умеющую маскироваться ошибку: код компилируется и работает, но в самый неподходящий момент, по закону подлости, ошибка скажет своё слово.



ИЗМЕНЕНИЕ ==>

Ошибку даже в маленьком коде сложно заметить. Этот пример утрирован, но тем не менее наглядно показывает, чего стоит опасаться. Этот пример написан благодаря ответу DrOffset на форуме cyberforum на мой вопрос.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/nikita-kultin/osnovy-programmirovaniya-v-turbo-c/?lfrom=15589587
Яндекс.Метрика