Урок №143. Конструкторы преобразования, ключевые слова explicit и delete

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 68042

 ǀ   13 

По умолчанию язык C++ обрабатывает любой конструктор, как оператор неявного преобразования. Рассмотрим следующую программу:

Хотя функция makeNegative() ожидает объект класса Drob, мы передаем ей целочисленный литерал 7. Поскольку у класса Drob есть конструктор, который может принимать одно целочисленное значение (конструктор по умолчанию), то компилятор выполнит неявную конвертацию литерала 7 в объект класса Drob. Это делается путем выполнения копирующей инициализации параметра d функции makeNegative() с помощью конструктора Drob(int, int).

Результат выполнения программы:

Copy constructor worked here!
-7/1

Неявное преобразование работает для всех видов инициализации (прямой, uniform и копирующей).

Конструкторы, которые используются в неявных преобразованиях, называются конструкторами преобразования (или «конструкторами конвертации»). До C++11 конструкторами преобразования могли быть конструкторы только с одним параметром. Однако в C++11 это ограничение было снято (наряду с добавлением uniform-инициализации), и конструкторы, имеющие несколько параметров, также уже могут быть конструкторами преобразования.

Ключевое слово explicit

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

В примере, приведенном выше, мы пытаемся инициализировать строку одним символом типа char. Поскольку переменные типа char являются частью семейства целочисленных типов, то компилятор будет использовать конструктор преобразования SomeString(int) для неявного преобразования символа типа char в тип SomeString. В результате переменная типа char будет конвертирована в тип int. А это не совсем то, что ожидается.

Один из способов решения этой проблемы — сделать конструктор явным, используя ключевое слово explicit (которое пишется перед именем конструктора). Явные конструкторы (с ключевым словом explicit) не используются для неявных конвертаций:

Вышеприведенная программа не скомпилируется, так как SomeString(int) мы сделали явным, а другого конструктора преобразования, который выполнил бы неявную конвертацию 'a' в SomeString, компилятор просто не нашел.

Однако использование явного конструктора только предотвращает выполнение неявных преобразований. Явные конвертации (через операторы явного преобразования) по-прежнему разрешены:

При прямой- или uniform-инициализации неявная конвертация также будет выполняться:

Правило: Для предотвращения возникновения ошибок с неявными конвертациями делайте ваши конструкторы явными, используя ключевое слово explicit.

Ключевое слово delete


Еще одним способом запретить конвертацию 'a' в SomeString (неявным или явным способом) является добавление закрытого конструктора SomeString(char):

Тем не менее, этот конструктор все еще может использоваться внутри класса (private закрывает доступ к данным только для объектов вне тела класса).

Лучшее решение — использовать ключевое слово delete (добавленное в C++11) для удаления этого конструктора:

После удаления функции, любое её использование вызовет ошибку компиляции.

Обратите внимание, конструктор копирования и перегруженные операторы также могут быть удалены с помощью delete для предотвращения их использования.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (198 оценок, среднее: 4,80 из 5)
Загрузка...

Комментариев: 13

  1. Тарас:

    Почему не используется этот конструктор

    Ведь мы передаем переменную типа char ?

    1. Михаил:

      Потому что в втором конструкторе аргумент(const char*) это указатель на char а не (char), нужно передать ссылку, и изменить сам конструктор
      с этого:

      на

      тогда все заработает. )

      1. Павел:

        Но мы же не можем получить адрес ‘a’ — это r-value

        Как передать в этот конструктор

        Только создать переменную типа char, и передать уже её адрес?

  2. Анатолий:

    Я тоже думал, что explicit это стандартный с++. да. и не все конструкторы следует писать с этим оператором. например не следует писать конструктор копирования с explicit. У компилятора сразу мозги на бикрень но ошибки не выдаст. Но когда запустите начнётся настоящая галиматья, которая проходит если убрать этот оператор.

  3. Анастасия:

    Я правильно поняла, что explicit спасёт от неявных конвертаций только при копирующей инициализации? В топку тогда такое ключевое слово, которое только при одном из трёх способов помогает.
    Насчёт "= delete" тоже как-то резануло… Почему нельзя было поставить это слово перед конструктором, как обычно

  4. Валерий:

    Что-то пошло не так.
    Фраза "Это делается путем выполнения копирующей инициализацией параметра d функции makeNegative() с помощью конструктора Drob(int, int)." не соответствует действительности.
    Добавляем деструктор с выводом и отладочную печать в контрольных точках.

    И получаем результат:

    Constructor worked here!
    Enter
    Leave
    Copy constructor worked here!
    Destructor
    -7/1
    Destructor
    Exit

    При вызове makeNegative(7) работает обычный конструктор, создавая анонимный объект. Объект передается в функцию. В функции происходит обработка. При выходе выполняется копирующая инициализация второго анонимного объекта. Первый объект удаляется. Производится вывод. Удаляется второй анонимный объект.

    1. Анастасия:

      да, Ваша демонстрация показательней, чем в уроке.

    2. ArtemF:

      При вызове makeNegative(7) компилируется в вызов makeNegative(), без аргументов, а 7 перейдёт в параметр, получится Drob makeNegative(Drob d = 7), а Drob d = 7 — это "копирующая инициализация" как и написано в тексте урока, только 7 это не объект, поэтому вместо к.копирования будет вызван к.преобразования(он же обычный к.по умолчанию), и только потом к.копирования при возврате объекта. В предыдущем уроке два раза вызывался к.копирования.
      Ваш пример с выводом действительно лучше, чем в уроке, так как видно что вместо к.копирования сработал к.преобразования о котором и идёт речь.

      1. Vlad:

        Всё верно.
        Конструктор копирования вызывается, только если уже есть объект, который нужно скопировать.
        А 7, это r-value, просто копирующая инициализация.

        1. Artem:

          А я думал, что конструктор копирования — это когда аргументом является такой же тип, как и у хозяина конструктора.

          MyClass () — конструктор по умолчанию (для инициализации массивов структур и классов)
          MyClass (YourClass, TheirClass) — конструктор инициализации (два+ аргумента)
          MyClass (YourClass) — Конструктор преобразования (вот не знаю обязательно ли ссылка)
          MyClass (MyClass&) — Конструктор копирования (обязательно ссылка)

  5. Slava:

    Привет! Мне нравится визуальный стиль оформления твоего листинга. Не мог бы ты скинуть данные по этому стилю, хочу его в CodeBlock засунуть.

    1. Фото аватара Юрий:

      Привет. Тема Monokai. Вот ссылка на цвета — Monokai.

      1. andrey:

        А шрифт какой?

Добавить комментарий для Юрий Отменить ответ

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