На этом уроке мы рассмотрим, что такое делегирующие конструкторы в языке С++, зачем они были придуманы и как их использовать.
Проблема
При создании нового объекта класса, компилятор C++ неявно вызывает конструктор этого объекта. Не редкость встретить класс с несколькими конструкторами, которые частично выполняют одно и то же, например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Boo { public: Boo() { // Часть кода X } Boo(int value) { // Часть кода X // Часть кода Y } }; |
Здесь есть 2 конструктора: конструктор по умолчанию и конструктор, который принимает целочисленное значение. Поскольку Часть кода X
требуется обоим конструкторам, то она дублируется в каждом из них.
А как вы уже могли догадаться, дублирование кода — это то, чего следует избегать, поэтому давайте рассмотрим возможные решения этой проблемы.
Решение в C++11
Неплохо было бы, чтобы конструктор Boo(int)
вызывал конструктор Boo() для выполнения Часть кода X
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Boo { public: Boo() { // Часть кода X } Boo(int value) { Boo(); // используем конструктор, указанный выше, для выполнения части кода X // Часть кода Y } }; |
Или:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Boo { public: Boo() { // Часть кода X } Boo(int value): Boo() // используем конструктор, указанный выше, для выполнения части кода X { // Часть кода Y } }; |
Однако, если ваш компилятор не совместим с C++11, и вы попытаетесь вызвать один конструктор внутри другого конструктора, то это скомпилируется, но будет работать не так, как вы ожидаете.
До C++11 явный вызов одного конструктора из другого приводит к созданию временного объекта, который затем инициализируется с помощью конструктора этого объекта и отбрасывается, оставляя исходный объект неизменным.
Использование отдельного метода
Конструкторам разрешено вызывать другие методы класса, которые не являются конструкторами. Хотя у вас может возникнуть соблазн скопировать код из первого конструктора во второй конструктор, наличие дублированного кода сделает ваш класс более трудным для понимания и более обременительным для поддержки. Лучшим решением будет создание отдельного метода (не конструктора), который будет выполнять общую инициализацию, и оба конструктора будут вызывать этот метод. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Boo { private: void DoX() { // Часть кода X } public: Boo() { DoX(); } Boo(int nValue) { DoX(); // Часть кода Y } }; |
Здесь мы свели дублирование кода к минимуму.
Кроме того, вы можете оказаться в ситуации, когда вам нужно будет написать метод для повторной инициализации класса обратно до значений по умолчанию. Поскольку у вас, вероятно, уже есть конструктор, который это делает, то у вас может возникнуть соблазн попытаться вызвать этот конструктор из вашего метода. Однако это приведет к неожиданным результатам. Многие разработчики просто копируют код из конструктора в функцию инициализации — это сработает, но приведет также к дублированию кода. Лучшим решением будет переместить код из конструктора в вашу новую функцию и заставить конструктор вызывать вашу новую функцию для выполнения инициализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Boo { public: Boo() { Init(); } Boo(int value) { Init(); // Делаем что-либо с value } void Init() { // Код инициализации Boo } }; |
Здесь мы подключаем функцию Init() для инициализации переменных-членов обратно значениями по умолчанию, а затем каждый конструктор вызывает функцию Init() перед своим фактическим выполнением. Это сокращает дублирование кода до минимума и позволяет явно вызывать Init() из любого места в программе.
Делегирующие конструкторы в C++11
Начиная с C++11, конструкторам разрешено вызывать другие конструкторы. Этот процесс называется делегированием конструкторов (или «цепочкой конструкторов»). Чтобы один конструктор вызывал другой, нужно просто сделать вызов этого конструктора в списке инициализации членов. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Boo { private: public: Boo() { // Часть кода X } Boo(int value): Boo() // используем конструктор по умолчанию Boo() для выполнения части кода X { // Часть кода Y } }; |
Всё работает как нужно. Убедитесь, что вы вызываете конструктор из списка инициализации членов, а не из тела конструктора.
Вот еще один пример использования делегирующих конструкторов для сокращения дублированного кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <iostream> #include <string> class Employee { private: int m_id; std::string m_name; public: Employee(int id=0, const std::string &name=""): m_id(id), m_name(name) { std::cout << "Employee " << m_name << " created.\n"; } // Используем делегирующие конструкторы для сокращения дублированного кода Employee(const std::string &name) : Employee(0, name) { } }; int main() { Employee a; Employee b("Ivan"); return 0; } |
Этот класс имеет 2 конструктора (один из которых вызывает другой). Таким образом, количество дублированного кода сокращено (нам нужно записать только одно определение конструктора вместо двух).
Несколько заметок о делегирующих конструкторах
Во-первых, конструктору, который вызывает другой конструктор, не разрешается выполнять какую-либо инициализацию членов класса. Поэтому конструкторы могут либо вызывать другие конструкторы, либо выполнять инициализацию, но не всё сразу.
Во-вторых, один конструктор может вызывать другой конструктор, в коде которого может находиться вызов первого конструктора. Это создаст бесконечный цикл и приведет к тому, что память стека закончится и произойдет сбой. Вы можете избежать этого, убедившись, что в конструкторе, который вызывается, нет вызова первого (и вообще любого другого) конструктора. Будьте аккуратны и не используйте вложенные вызовы конструкторов.
>>конструктору, который вызывает другой конструктор, не разрешается выполнять какую-либо инициализацию членов класса;
Странно, у меня работает. После конструктора ставлю запятую и пишу список инициализации. С++ 20 GCC.
А я думал что это входит в стандартный C++ Однако в Шилдт(4-е издание) я того почему-то не нашёл. А оказывается это стандарт C++11. а то не знаю откуда то ,чем активно пользуюсь много лет. это оч удобно кстати. Рекомендую…
Как в последнем примере создаётся объект Emploee a, если конструктор без параметров отсутствует?
Там указаны параметры по умолчанию
Тут мы фактически передаем в эту функции все действия, которые происходят в конструкторах? Поправьте, если ошибаюсь.
в функцию поместили код инициализации для полей с именем и кодом.
например:
конструкторы вызывать эту функцию внутри тем самым не дублируя код. Иначе говоря мы писали бы этот код в каждом конструкторе "this->name="jack"; this->id=1;"
А параметры функции тогда зачем? Да еще и имена совпадают с полями, зачем так сбивать с толку, особенно когда *this будет только через 2 урока
В последнем примере, в строке с
не должно быть Employee(id, name)? Зачем в этом "вложении" принудительно присваивать 0?
Потому что он не получает id. У него есть один параметр, а другому конструктору нужно передать два.
Я конечно все понял , но не понял одного , зачем одному конструктору вызывать другой конструктор
Из разряда того, что мы установим тебе монитор в мониторе, чтобы ты мог смотреть фильм пока смотришь другой фильм. (West cost…) А так, то я тоже не понимаю зачем нужен конструктор в конструкторе. Что мешает создавать третий пятый десятый уровень подконструкторов…
Для того чтобы не писать один и тот же код много раз.
1. Так и не понял, почему нельзя вложить конструктор в тело конструктора в с++11…
Работает как нужно. А вы пишите так не делать. По мне, так проще читается. Может в производительности разница, или в чем подвох?
2. Грамматические ошибки в последних абзацах — режет восприятие текста:
"Во-первых, конструктору, который вызывает другого конструктора"
"Во-вторых, один конструктор может вызывать другого конструктора".
Винительный падеж — в обоих случаях должно быть "другой конструктор".
Ошибки исправил, спасибо)
да, дело в производительности. 1 вариант работает быстрее.
Так вы создадите временный объект. Делегирующие конструкторы не могут быть рекурсивными.
А можно как-нибудь таким способом не создавать временный объект, а делегировать конструктор?