Урок №119. Делегирующие конструкторы

  Юрий  | 

  |

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

 73378

 ǀ   17 

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

Проблема

При создании нового объекта класса, компилятор C++ неявно вызывает конструктор этого объекта. Не редкость встретить класс с несколькими конструкторами, которые частично выполняют одно и то же, например:

Здесь есть 2 конструктора: конструктор по умолчанию и конструктор, который принимает целочисленное значение. Поскольку Часть кода X требуется обоим конструкторам, то она дублируется в каждом из них.

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

Решение в C++11


Неплохо было бы, чтобы конструктор Boo(int) вызывал конструктор Boo() для выполнения Часть кода X:

Или:

Однако, если ваш компилятор не совместим с C++11, и вы попытаетесь вызвать один конструктор внутри другого конструктора, то это скомпилируется, но будет работать не так, как вы ожидаете.

До C++11 явный вызов одного конструктора из другого приводит к созданию временного объекта, который затем инициализируется с помощью конструктора этого объекта и отбрасывается, оставляя исходный объект неизменным.

Использование отдельного метода

Конструкторам разрешено вызывать другие методы класса, которые не являются конструкторами. Хотя у вас может возникнуть соблазн скопировать код из первого конструктора во второй конструктор, наличие дублированного кода сделает ваш класс более трудным для понимания и более обременительным для поддержки. Лучшим решением будет создание отдельного метода (не конструктора), который будет выполнять общую инициализацию, и оба конструктора будут вызывать этот метод. Например:

Здесь мы свели дублирование кода к минимуму.

Кроме того, вы можете оказаться в ситуации, когда вам нужно будет написать метод для повторной инициализации класса обратно до значений по умолчанию. Поскольку у вас, вероятно, уже есть конструктор, который это делает, то у вас может возникнуть соблазн попытаться вызвать этот конструктор из вашего метода. Однако это приведет к неожиданным результатам. Многие разработчики просто копируют код из конструктора в функцию инициализации — это сработает, но приведет также к дублированию кода. Лучшим решением будет переместить код из конструктора в вашу новую функцию и заставить конструктор вызывать вашу новую функцию для выполнения инициализации:

Здесь мы подключаем функцию Init() для инициализации переменных-членов обратно значениями по умолчанию, а затем каждый конструктор вызывает функцию Init() перед своим фактическим выполнением. Это сокращает дублирование кода до минимума и позволяет явно вызывать Init() из любого места в программе.

Делегирующие конструкторы в C++11


Начиная с C++11, конструкторам разрешено вызывать другие конструкторы. Этот процесс называется делегированием конструкторов (или «цепочкой конструкторов»). Чтобы один конструктор вызывал другой, нужно просто сделать вызов этого конструктора в списке инициализации членов. Например:

Всё работает как нужно. Убедитесь, что вы вызываете конструктор из списка инициализации членов, а не из тела конструктора.

Вот еще один пример использования делегирующих конструкторов для сокращения дублированного кода:

Этот класс имеет 2 конструктора (один из которых вызывает другой). Таким образом, количество дублированного кода сокращено (нам нужно записать только одно определение конструктора вместо двух).

Несколько заметок о делегирующих конструкторах

Во-первых, конструктору, который вызывает другой конструктор, не разрешается выполнять какую-либо инициализацию членов класса. Поэтому конструкторы могут либо вызывать другие конструкторы, либо выполнять инициализацию, но не всё сразу.

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


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

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

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

  1. Vuniverse:

    >>конструктору, который вызывает другой конструктор, не разрешается выполнять какую-либо инициализацию членов класса;
    Странно, у меня работает. После конструктора ставлю запятую и пишу список инициализации. С++ 20 GCC.

  2. Анатолий:

    А я думал что это входит в стандартный C++ Однако в Шилдт(4-е издание) я того почему-то не нашёл. А оказывается это стандарт C++11. а то не знаю откуда то ,чем активно пользуюсь много лет. это оч удобно кстати. Рекомендую…

  3. Ivan:

    Как в последнем примере создаётся объект Emploee a, если конструктор без параметров отсутствует?

    1. Валера:

      Там указаны параметры по умолчанию

  4. Алексей:

    Тут мы фактически передаем в эту функции все действия, которые происходят в конструкторах? Поправьте, если ошибаюсь.

    1. Вова:

      в функцию поместили код инициализации для полей с именем и кодом.
      например:

      конструкторы вызывать эту функцию внутри тем самым не дублируя код. Иначе говоря мы писали бы этот код в каждом конструкторе "this->name="jack"; this->id=1;"

      1. Сергей:

        А параметры функции тогда зачем? Да еще и имена совпадают с полями, зачем так сбивать с толку, особенно когда *this будет только через 2 урока

  5. somebox:

    В последнем примере, в строке с

    не должно быть Employee(id, name)? Зачем в этом "вложении" принудительно присваивать 0?

    1. otecart:

      Потому что он не получает id. У него есть один параметр, а другому конструктору нужно передать два.

  6. SuRprizZe:

    Я конечно все понял , но не понял одного , зачем одному конструктору вызывать другой конструктор

    1. Всеволод:

      Из разряда того, что мы установим тебе монитор в мониторе, чтобы ты мог смотреть фильм пока смотришь другой фильм. (West cost…) А так, то я тоже не понимаю зачем нужен конструктор в конструкторе. Что мешает создавать третий пятый десятый уровень подконструкторов…

      1. Сергей:

        Для того чтобы не писать один и тот же код много раз.

  7. kmish:

    1. Так и не понял, почему нельзя вложить конструктор в тело конструктора в с++11…

    Работает как нужно. А вы пишите так не делать. По мне, так проще читается. Может в производительности разница, или в чем подвох?
    2. Грамматические ошибки в последних абзацах — режет восприятие текста:
    "Во-первых, конструктору, который вызывает другого конструктора"
    "Во-вторых, один конструктор может вызывать другого конструктора".
    Винительный падеж — в обоих случаях должно быть "другой конструктор".

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

      Ошибки исправил, спасибо)

    2. Андрей:

      да, дело в производительности. 1 вариант работает быстрее.

    3. jkp375:

      Так вы создадите временный объект. Делегирующие конструкторы не могут быть рекурсивными.

      1. Сергей:

        А можно как-нибудь таким способом не создавать временный объект, а делегировать конструктор?

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

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