Пользовательские преобразования типов: приведение объекта самодельного класса

C++ по возможности проводит неявные преобразования встроенных типов. Когда переменная одного типа получает в себя значение переменной другого типа, путём присваивания, то если фигурирующие в процессе типы тесно связаны друг с другом, С++ неявно приводит тип принимаемой переменной к типу принимающей переменной:

         int x = 9.33;      //тип int принимающий, тип double принимаемый, тип double преобразуется к типу int
         long count = 9;    //тип long принимающий, тип int принимаемы, тип int неявно преобразуется к типу long
         double z = 7;      //тип double принимающий, тип int принимаемы, тип int неявно преобразуется к типу double
         

С++ распознает, что в показанном варианте фигурирующие в выражениях типы представляют собой целые числа. Поскольку С++ поддерживает встроенные правила для преобразования между встроенными типам-числами, происходят такие приведения. Многим из моих читатетелй это уже прекрасно известно.

Если типы не совместимы или по логике или по смыслу, то такие типы неявно не преобразуются:

        int *arr = 10;     //принимающий тип - (указатель на int), принимаемый тип int, тип int* к типу int неявно не преобразуется
    

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

        int *arr = (int*)10;    //явное преобразование в стиле С, int преобразуется к int* (тип числа 10 преобразуется к адресу 10)
    

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

        // int x = "Hello";     //принимающий тип int, принимаемый тип const char*, неявно не приводится
        // int x = (int)"Hello";     //принимающий тип int, принимаемый тип const char*, явно не приводится
    

  • Если определяемый класс в достаточной мере связан с базовым типом или другим классом, преобразование одного в другой имеет смысл. В этом случае можно указать C++ как выполнять преобразование автоматически, либо, возможно, через приведение типа.
В случае преобразования некоторого типа к типу объекта нашего класса, можно использовать конвертирующий конструктор, но при этом обратное преобразование надр будет, если оно понадобится, описывать дополнительно:


  • Конструктор C++, который принимает один аргумент, определяет преобразование типа аргумента в тип класса. Если конструктор снабжен ключевым словом explicit, он может использоваться только с явной формой преобразования, в противном случае допускается неявное преобразование. В последних версиях действующих стандартов (начиная с С++11) конвертирующий конструктор может иметь несколько параметров или не иметь параметров вообще.
О преобразовании некоторого типа к типу класса можно почитать в статье Конструктор с параметрами. Конвертирующий конструктор и ключевое слово explicit. Настоящая статья немного о другом виде преобразований. О тех преобразованиях, которые не произошли в листинге #1, хотя по чьей-нибудь логике должны были сработать, ведь иначе дискриминация типов какая-то получается. С++ позволяет разрешить такую проблему. Для того, чтобы всё это заработало, надо наш класс научить преобразовывать типы.
Чтобы научить объект класса приводится к какому-то типу, нужно использовать вот такую конструкцию:
При этом стоит следовать вот таким правилам:

  • функция преобразования должна быть методом класса;
  • в функции преобразования не должен быть указан возвращаемый тип;
  • функция преобразования не должна иметь аргументов.
Есть понятие перегрузки операций, которое сразу могло прийти в голову при виде ключевого слова operator. В нашем случае это не перегрузка операций, а то, что будет учить наш класс приводить свои объекты к указываемому нами типу, сейчас к int. После ключевого слова operator нужно написать тип, после которого поставить формальные круглые скобки. Нужно ещё добавить тело в фигурных скобках, в нём можно описать какие-то действия и обязательно нужно вернуть значение. Возвращаемое значение должно иметь совместимый тип с типом, указанным после слова operator. Если мы учим класс приводить объект к типу int, то и возвращать из мозга класса надо тип int, что вообще-то логично. Если бы это был string, то вот так:


Подводим промежуточные итоги:

  • С++ умеет преобразовывать типы.
  • С++ умеет преобразовывать типы неявно или явно.
  • С++ даёт возможность программисту создавать собственные типы.
  • С++ даёт возможность программисту описывать и использовать преобразования для созданных им типов.
  • Принимаемый тип может преобразовываться к выгодному себе типу, процессом преобразования руководит принимающий тип.
  • Один из способов преобразований между типами — использование конвертирующего конструктора. Такой конструктор преобразует входящий или входящие в себя типы в тип родного себе класса.
  • Другой способ преобразований — использование ключевого слова operator, к которому справа подсказывается, к какому типу приводим объекты описываемого класса, и приписываем формальные круглые скобки.
Помотрим цепочки происходящих преобразований. Чтобы понимать что-то, нужно понимать о неявных приведениях хоть что-нибудь:


Поскольку целые числа умеют между собой приводится неявно, в листинге #2 местами происходт целая цепь событий: приведения паравозиком. Если бы невное приведение было не разрешено, то цепочка могла бы сорваться. Но важнее сейчас, чтобы вы увидели, что то, что не работало в листинге #1 заработало в листинге #2: взаимозаменяемое присваивание целого числа и объекта нашего самодельного класса.

Поскольку неявные приведения не всегда желательны, к тому же вы могли заметить, что иногда происходит целая цепочка приведений, можно запретить выполнять приведения неявно, оставив возможность только явного осуществления такого механизма. Для этих целей используют ключевое слово explicit.
Использованием ключевого слова operator можно описывать взаимодествие не только между объектами некоторых классов и встроенными типами, но и объектов некотрых классов с объектами других классов. Всё-таки объекты классов — это те же типы, только создаваемые нами:

Если мы не запретим конвертирующий конструктор в листинге #4, то вместо того, чтобы увидеть работу ключевого слова operator в качестве обучающей части класса преобразованию объектов к иному типу, мы увидим срабатывание конструктора. Это нам совсем не нужно. А кроме этого, в показанном варианте мы не можем оставить определение обучения преобразования внутри класса, возникнет синтаксическая ошибка, поэтому нам необходимо выносить определение обучения за тело класса. Это обусловлено тем, что в получившемся коде мы возвращаем временные объекты, получаемые путём явного вызова конструкторов, но классам не хватает информации, чтобы запустить сборку объектов указанными конструкторами.

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

Как можете увидеть, использование явного преобразования типов никак не мешает работе, а заодно сам процесс нам более очевиден. Таинственности никакой: всё видно, что к чему и когда преобразуется. Если вы будете применять показанные приёмы в ваших кодах, вам только надо будет научить классы правильно выводится один из другого. Этот процесс немного напоминает частичное копирование объектов: копируете нужные значения из приводимого объекта в выводимый преобразованием объект.
В завершение статьи реализуем самодельный класс наподобие string, который будет приниматься целым числом:
ВНИМАНИЕ!

  • Если вы используете Visual Studio, то могут возникнуть небольшие проблемы навязчивости, язык С++ тут ни при чём. Если у вас будет происходить ругательство на strcpy, если вы не знаете как это исправить, то можете подсмотреть видео: https://youtu.be/NZW7djD3mH8

В видео показано, куда вписывать _CRT_SECURE_NO_WARNINGS
Если там что-то будет вписано, то нужно будет испольовать точку с запятой как разделитель.
Свойства проекта -> С/С++ -> Препроцессор -> Опеределения препроцессора

Возвращаемся к примерам. Изучаем код:

Этот последний код не очень практичен. Да и при некотором развитии своём может запутать. Если в строку, например, присваивать напрямую числовое значение, а не строковое, то можно легко запутаться. К счастью, в данном случае присваивать в строку число напрямую нельзя, потому что строка и число по смыслу сильно отличные друг от друга типы, но у непоседливого читателя может появиться ярое желание перегрузить операцию присваивания, чтобы присваивание работало в обоих направлениях. Хоть такое и возможно, такое идеологически ошибочно. В нашем же случае могут произойти ошибки (по сути не ошибки, для нас могут выглядеть как ошибки), если мы будем отдавать на обработку строковое строку не являющуюя числом, хотя подобное будет работать, расчитать, что получится в конечном итоге будет слегка тяжеловато (особенно тем, кто плохо понимает, что происходит).


Но как бы то ни было, моя цель донести возможность пользовательских преобразований, показать возможность взаимодействия объектов и научение их выводить из своих объектов значения возможно затребованных в коде типов.
Можно дописать операцию присваивания, но помните, что данный пример непрактичен, он показывается вам только с целью донесения информация: вот как можно, вот как оно делается.

На случай необходимости инициализации должен быть описан подходящий конструктор, если вдруг кого озаботит маленькое ограничение, которое, возможно, обнаружится непоседой.
Использованные материалы:

Язык программирования C++. Лекции и упражнения. 6-е изд (Стивен Прата)

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

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

Поиск

 
     

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

https://www.litres.ru/endru-tanenbaum/operacionnye-sistemy-razrabotka-i-realizaciya-15418253/?lfrom=15589587
Яндекс.Метрика