Перейти к содержимому

Зачем нужен конструктор по умолчанию c

  • автор:

Зачем нужен конструктор по умолчанию c

Конструкторы представляют специальную функцию, которая имеет то же имя, что и класс, которая не возвращает никакого значения и которая позволяют инициалилизировать объект класса во время го создания и таким образом гарантировать, что поля класса будут иметь определенные значения. При каждом создании нового объекта класса вызывается конструктор класса.

В прошлой теме был разработан следующий класс:

#include class Person < public: std::string name; unsigned age; void print() < std::cout >; int main() < Person person; // вызов конструктора person.name = "Tom"; person.age = 22; person.print(); >

Здесь при создании объекта класса Person, который называется person

Person person;

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

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

#include class Person < public: std::string name; unsigned age; void print() < std::cout Person(std::string p_name, unsigned p_age) < name = p_name; age = p_age; std::cout >; int main() < Person tom("Tom", 38); // создаем объект - вызываем конструктор tom.print(); >

Теперь в классе Person определен конструктор:

Person(std::string p_name, unsigned p_age)

По сути конструктор представляет функцию, которая может принимать параметры и которая должна называться по имени класса. В данном случае конструктор принимает два параметра и передает их значения полям name и age, а затем выводит сообщение о создании объекта.

Если мы определяем свой конструктор, то компилятор больше не создает конструктор по умолчанию. И при создании объекта нам надо обязательно вызвать определенный нами конструктор.

Вызов конструктора получает значения для параметров и возвращает объект класса:

Person tom("Tom", 38);

После этого вызова у объекта person для поля name будет определено значение «Tom», а для поля age — значение 38. Вполедствии мы также сможем обращаться к этим полям и переустанавливать их значения.

В качестве альтернативы для создания объекта можно использовать инициализатор в фигурных скобках:

Person tom;

Тажке можно присвоить объекту результат вызова конструктора:

Person tom = Person("Tom", 38);

По сути она будет эквивалетна предыдущей.

Консольный вывод определенной выше программы:

Person has been created Name: Tom Age: 38

Конструкторы облегчают нам создание нескольких объектов, которые должны иметь разные значения:

#include class Person < public: std::string name; unsigned age; void print() < std::cout Person(std::string p_name, unsigned p_age) < name = p_name; age = p_age; std::cout >; int main() < Person tom; Person bob; Person sam; tom.print(); bob.print(); sam.print(); >

Здесь создаем три разных объекта класса Person (условно трех разных людей), и соответственно в данном случае консольный вывод будет следующим:

Person has been created Person has been created Person has been created Name: Tom Age: 38 Name: Bob Age: 42 Name: Sam Age: 25

Определение нескольких конструкторов

Подобным образом мы можем определить несколько конструкторов и затем их использовать:

#include class Person < std::string name<>; unsigned age<>; public: void print() < std::cout Person(std::string p_name, unsigned p_age) < name = p_name; age = p_age; >Person(std::string p_name) < name = p_name; age = 18; >Person() < name = "Undefined"; age = 18; >>; int main() < Person tom; // вызываем конструктор Person(std::string p_name, unsigned p_age) Person bob; // вызываем конструктор Person(std::string p_name) Person sam; // вызываем конструктор Person() tom.print(); bob.print(); sam.print(); >

В классе Person определено три конструктора, и в функции все эти конструкторы используются для создания объектов:

Name: Tom Age: 38 Name: Bob Age: 18 Name: Undefined Age: 18

Хотя пример выше прекрасно работает, однако мы можем заметить, что все три конструктора выполняют фактически одни и те же действия — устанавливают значения переменных name и age. И в C++ можем сократить их определения, вызова из одного конструктора другой и тем самым уменьшить объем кода:

#include class Person < std::string name<>; unsigned age<>; public: void print() < std::cout Person(std::string p_name, unsigned p_age) < name = p_name; age = p_age; std::cout Person(std::string p_name): Person(p_name, 18) // вызов первого конструктора < std::cout Person(): Person(std::string("Undefined")) // вызов второго конструктора < std::cout >; int main() < Person sam; // вызываем конструктор Person() sam.print(); >

Запись Person(string p_name): Person(p_name, 18) представляет вызов конструктора, которому передается значение параметра p_name и число 18. То есть второй конструктор делегирует действия по инициализации переменных первому конструктору. При этом второй конструктор может дополнительно определять какие-то свои действия.

Таким образом, следующее создание объекта

Person sam;

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

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

Параметры по умолчанию

Как и другие функции, конструкторы могут иметь параметры по умолчанию:

#include class Person < std::string name; unsigned age; public: // передаем значения по умолчанию Person(std::string p_name = "Undefined", unsigned p_age = 18) < name = p_name; age = p_age; >void print() < std::cout >; int main() < Person tom; Person bob; Person sam; tom.print(); // Name: Tom Age: 38 bob.print(); // Name: Bob Age: 18 sam.print(); // Name: Undefined Age: 18 >

Инициализация констант и списки инициализации

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

class Person < const std::string name; unsigned age<>; public: void print() < std::cout Person(std::string p_name, unsigned p_age) < name = p_name; age = p_age; >>;

Этот класс не будет компилироваться из-за отсутствия инициализации константы name. Хотя ее значение устанавливается в конструкторе, но к моменту, когда инструкции из тела конструктора начнут выполняться, константы уже должны быть инициализированы. И для этого необходимо использовать списки инициализации :

#include class Person < const std::string name; unsigned age<>; public: void print() < std::cout Person(std::string p_name, unsigned p_age) : name  < age = p_age; >>; int main() < Person tom; tom.print(); // Name: Tom Age: 38 >

Списки инициализации представляют перечисления инициализаторов для каждой из переменных и констант через двоеточие после списка параметров конструктора:

Person(std::string p_name, unsigned p_age) : name

Здесь выражение name позволяет инициализировать константу значением параметра p_name. Здесь значение помещается в фигурные скобки, но также можно использовать кргулые:

Person(std::string p_name, unsigned p_age) : name(p_name)

Списки инициализации пободным образом можно использовать и для присвоения значений переменным:

class Person < const std::string name; unsigned age; public: void print() < std::cout Person(std::string p_name, unsigned p_age) : name(p_name), age(p_age) < >>;

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

Конструкторы (C++)

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

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

class Box < public: // Default constructor Box() <>// Initialize a Box with equal dimensions (i.e. a cube) explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list <> // Initialize a Box with custom dimensions Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> int Volume() < return m_width * m_length * m_height; >private: // Will have value of 0 when default constructor is called. // If we didn't zero-init here, default constructor would // leave them uninitialized with garbage values. int m_width< 0 >; int m_length< 0 >; int m_height< 0 >; >; 

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

int main() < Box b; // Calls Box() // Using uniform initialization (preferred): Box b2 ; // Calls Box(int) Box b3 ; // Calls Box(int, int, int) // Using function-style notation: Box b4(2, 4, 6); // Calls Box(int, int, int) > 
  • Конструкторы могут быть объявлены как inline , friendexplicit или constexpr .
  • Конструктор может инициализировать объект, объявленный как const или volatileconst volatile . Объект становится const после завершения конструктора.
  • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любая другая функция-член: Box::Box()

Списки инициализатора элементов

Конструктор может иметь список инициализаторов элементов, который инициализирует элементы класса перед запуском текста конструктора. (Список инициализаторов элементов не совпадает со списком инициализаторов типа std::initializer_list .)

Предпочесть списки инициализаторов элементов вместо назначения значений в тексте конструктора. Список инициализаторов элементов напрямую инициализирует элементы. В следующем примере показан список инициализатора элементов, состоящий из всех identifier(argument) выражений после двоеточия:

 Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> 

Идентификатор должен ссылаться на член класса; он инициализирован со значением аргумента. Аргумент может быть одним из параметров конструктора, вызовом функции или одним std::initializer_list из них.

const элементы и члены ссылочного типа должны быть инициализированы в списке инициализатора элементов.

Чтобы обеспечить полное инициализацию базовых классов перед запуском производного конструктора, вызовите все параметризованные конструкторы базового класса в списке инициализатора.

Конструкторы по умолчанию

У конструкторов по умолчанию обычно нет параметров, но у них могут быть параметры со значениями по умолчанию.

class Box < public: Box() < /*perform any required default initialization steps*/>// All params have default values Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l)<> . > 

Конструкторы по умолчанию являются одной из специальных функций-членов. Если конструкторы не объявляются в классе, компилятор предоставляет неявный inline конструктор по умолчанию.

#include using namespace std; class Box < public: int Volume() private: int m_width < 0 >; int m_height < 0 >; int m_length < 0 >; >; int main() < Box box1; // Invoke compiler-generated constructor cout 

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

Можно запретить компилятору создавать неявный конструктор по умолчанию, определив его как deleted:

 // Default constructor Box() = delete; 

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

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

class myclass<>; int main() < myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?) >

Это оператор является примером проблемы "Большинство Vexing Parse". Можно интерпретировать myclass md(); как объявление функции или как вызов конструктора по умолчанию. Так как средства синтаксического анализа C++ предпочитают объявления по сравнению с другими вещами, выражение рассматривается как объявление функции. Дополнительные сведения см. в статье Википедии Наиболее неоднозначный анализ.

Если объявлены какие-либо конструкторы, отличные от по умолчанию, компилятор не предоставляет конструктор по умолчанию:

class Box < public: Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height)<>private: int m_width; int m_length; int m_height; >; int main()< Box box1(1, 2, 3); Box box2< 2, 3, 4 >; Box box3; // C2512: no appropriate default constructor available > 

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

Box boxes[3]; // C2512: no appropriate default constructor available 

Но для инициализации массива объектов Box можно использовать набор списков инициализаторов:

Box boxes[3]< < 1, 2, 3 >, < 4, 5, 6 >, < 7, 8, 9 >>; 

Дополнительные сведения см. в статье Инициализаторы.

Конструкторы копии

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

У конструктора копий может быть одна из следующих сигнатур:

 Box(Box& other); // Avoid if possible--allows modification of other. Box(const Box& other); Box(volatile Box& other); Box(volatile const Box& other); // Additional parameters OK if they have default values Box(Box& other, int i = 42, string label = "Box"); 

При определении конструктора копий необходимо также определить оператор присваивания копированием (=). Дополнительные сведения см. в статьях Присваивание и Конструкторы копий и операторы присваивания копированием.

Вы можете запретить копирование объекта, определив конструктор копий как удаленный:

 Box (const Box& other) = delete; 

При попытке копирования объекта возвращается ошибка C2280: предпринята попытка ссылки на удаленную функцию.

Конструкторы перемещения

Конструктор перемещения — это специальная функция-член, передающая право собственности на данные существующего объекта новой переменной без копирования исходных данных. Он принимает ссылку rvalue в качестве первого параметра, а все последующие параметры должны иметь значения по умолчанию. Конструкторы перемещения могут значительно повысить эффективность программы при передаче больших объектов.

Box(Box&& other); 

Компилятор выбирает конструктор перемещения, когда объект инициализирован другим объектом того же типа, если другой объект будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показан один случай, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение будет xvalue (значение с истекающим сроком действия). Поэтому он не назначается какой-либо переменной и поэтому выходит из область. Чтобы создать основу для этого примера, пусть у объекта Box будет большой вектор строк, которые представляют его содержимое. Вместо копирования вектора и его строк конструктор перемещения "крадет" его из значения с истекающим сроком действия box, чтобы вектор теперь принадлежал новому объекту. Необходим только вызов std::move , так как классы vector и string реализуют собственные конструкторы перемещения.

#include #include #include #include using namespace std; class Box < public: Box() < std::cout Box(int width, int height, int length) : m_width(width), m_height(height), m_length(length) < std::cout Box(Box& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length) < std::cout Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length) < m_contents = std::move(other.m_contents); std::cout int Volume() < return m_width * m_height * m_length; >void Add_Item(string item) < m_contents.push_back(item); >void Print_Contents() < for (const auto& item : m_contents) < cout > private: int m_width< 0 >; int m_height< 0 >; int m_length< 0 >; vector m_contents; >; Box get_Box() < Box b(5, 10, 18); // "int,int,int" b.Add_Item("Toupee"); b.Add_Item("Megaphone"); b.Add_Item("Suit"); return b; >int main() < Box b; // "default" Box b1(b); // "copy" Box b2(get_Box()); // "move" cout > ch; // keep window open return 0; > 

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

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

Дополнительные сведения о написании нетривиального конструктора перемещения см. в статье Конструкторы перемещения и операторы присваивания перемещением.

Явные конструкторы по умолчанию и удаленные конструкторы

Вы можете явно создавать конструкторы копий по умолчанию, конструкторы по умолчанию, конструкторы перемещения, операторы присваивания копированием, операторы присваивания перемещением и деструкторы. Вы можете явно удалить все специальные функции-члены.

class Box2 < public: Box2() = delete; Box2(const Box2& other) = default; Box2& operator=(const Box2& other) = default; Box2(Box2&& other) = default; Box2& operator=(Box2&& other) = default; //. >; 

Конструкторы constexpr

Конструктор может быть объявлен как constexpr в следующих случаях:

  • он либо объявлен как стандартный, либо он удовлетворяет всем условиям для функций constexpr в целом;
  • у класса нет виртуальных базовых классов;
  • каждый из параметров является типом литерала;
  • Текст не является блоком пробной функции;
  • инициализированы все нестатические члены данных и подобъекты базового класса;
  • если класс является a) объединением с вариантами членов или б) имеет анонимные объединения, то инициализируется только одно из объединений;
  • каждый нестатический член данных типа класса, а все подобъекты базового класса имеют конструктор constexpr

Конструкторы списков инициализаторов

 Box(initializer_list list, int w = 0, int h = 0, int l = 0) : m_contents(list), m_width(w), m_height(h), m_length(l) <> 

Затем создайте объекты Box следующим образом:

 Box b< "apples", "oranges", "pears" >; // or . Box b2(initializer_list < "bread", "cheese", "wine" >, 2, 4, 6); 

Явные конструкторы

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

Box(int size): m_width(size), m_length(size), m_height(size)<> 

Можно инициализировать Box следующим образом:

Box b = 42; 

Или передать целое значение функции, принимающей объект Box:

class ShippingOrder < public: ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage)<>private: Box m_box; double m_postage; > //elsewhere. ShippingOrder so(42, 10.8); 

В некоторых случаях подобные преобразования могут быть полезны, однако чаще всего они могут привести к незаметным, но серьезным ошибкам в вашем коде. В качестве общего правила следует использовать explicit ключевое слово конструктора (и определяемых пользователем операторов), чтобы предотвратить такое неявное преобразование типов:

explicit Box(int size): m_width(size), m_length(size), m_height(size)<> 

Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе Заданные пользователем преобразования типов (C++).

Порядок строительства

Конструктор выполняет свою работу в следующем порядке.

  1. Вызывает конструкторы базовых классов и членов в порядке объявления.
  2. Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.
  3. Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.
  4. Выполняет весь код в теле функции.

В следующем примере показан порядок, в котором конструкторы базовых классов и членов вызываются в конструкторе для производного класса. Сначала вызывается базовый конструктор. Затем члены базового класса инициализированы в порядке, в котором они отображаются в объявлении класса. Наконец, вызывается производный конструктор.

#include using namespace std; class Contained1 < public: Contained1() < cout >; class Contained2 < public: Contained2() < cout >; class Contained3 < public: Contained3() < cout >; class BaseContainer < public: BaseContainer() < cout private: Contained1 c1; Contained2 c2; >; class DerivedContainer : public BaseContainer < public: DerivedContainer() : BaseContainer() < cout private: Contained3 c3; >; int main()

Появятся следующие выходные данные.

Contained1 ctor Contained2 ctor BaseContainer ctor Contained3 ctor DerivedContainer ctor 

Конструктор производного класса всегда вызывает конструктор базового класса, чтобы перед выполнением любых дополнительных операций иметь в своем распоряжении полностью созданные базовые классы. Конструкторы базового класса вызываются в порядке наследования — например, если ClassA является производным от ClassB , который является производным от ClassC , сначала вызывается конструктор ClassC , затем конструктор ClassB и последним конструктор ClassA .

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

class Box < public: Box(int width, int length, int height)< m_width = width; m_length = length; m_height = height; >private: int m_width; int m_length; int m_height; >; class StorageBox : public Box < public: StorageBox(int width, int length, int height, const string label&) : Box(width, length, height)< m_label = label; >private: string m_label; >; int main()

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

  1. Отменяется код в теле функции конструктора.
  2. Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.
  3. Если конструктор не делегируется, все полностью созданные объекты базового класса и члены уничтожаются. Однако, поскольку сам объект не полностью построен, деструктор не выполняется.

Производные конструкторы и расширенная инициализация агрегатов

Если конструктор базового класса не является открытым, но доступен для производного класса, то вы не можете использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версий.

В следующем примере показана соответствующая реакция на событие в C++14:

struct Derived; struct Base < friend struct Derived; private: Base() <>>; struct Derived : Base <>; Derived d1; // OK. No aggregate init involved. Derived d2 <>; // OK in C++14: Calls Derived::Derived() // which can call Base ctor. 

В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Ранее частный Base конструктор был вызван с помощью Derived конструктора, и он успешно выполнен из-за friend объявления.

В следующем примере показано поведение C++17 в Visual Studio 2017 и более поздних версиях в режиме /std:c++17 :

struct Derived; struct Base < friend struct Derived; private: Base() <>>; struct Derived : Base < Derived() <>// add user-defined constructor // to call with <> initialization >; Derived d1; // OK. No aggregate init involved. Derived d2 <>; // error C2248: 'Base::Base': can't access // private member declared in class 'Base' 

Конструкторы классов с множественным наследованием

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

#include using namespace std; class BaseClass1 < public: BaseClass1() < cout >; class BaseClass2 < public: BaseClass2() < cout >; class BaseClass3 < public: BaseClass3() < cout >; class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 < public: DerivedClass() < cout >; int main()

Должны выводиться следующие выходные данные:

BaseClass1 ctor BaseClass2 ctor BaseClass3 ctor DerivedClass ctor 

Делегирующие конструкторы

Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторых операций инициализации. Эта функция полезна при наличии нескольких конструкторов, которые должны выполнять аналогичную работу. Можно написать основную логику в одном конструкторе и вызвать ее из других. В следующем упрощенном примере Box(int) делегирует свою работу Box(int,int,int):

class Box < public: // Default constructor Box() <>// Initialize a Box with equal dimensions (i.e. a cube) Box(int i) : Box(i, i, i) // delegating constructor <> // Initialize a Box with custom dimensions Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> //. rest of class as before >; 

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

Наследование конструкторов (C++11)

Производный класс может наследовать конструкторы из прямого базового класса с помощью using объявления, как показано в следующем примере:

#include using namespace std; class Base < public: Base() < cout Base(const Base& other) < cout explicit Base(int i) : num(i) < cout explicit Base(char c) : letter(c) < cout private: int num; char letter; >; class Derived : Base < public: // Inherit all constructors from Base using Base::Base; private: // Can't initialize newMember from Base constructors. int newMember< 0 >; >; int main() < cout /* Output: Derived d1(5) calls: Base(int) Derived d1('c') calls: Base(char) Derived d3 = d2 calls: Base(Base&) Derived d4 calls: Base()*/ 

Visual Studio 2017 и более поздних версий: оператор using в режиме и более поздних версиях преобразуется в /std:c++17 область все конструкторы базового класса, кроме тех, которые имеют идентичную сигнатуру конструкторам в производном классе. Как правило, рекомендуется использовать наследующие конструкторы, когда производный класс не объявляет новых элементов данных или конструкторов.

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

template < typename T >class Derived : T < using T::T; // declare the constructors from T // . >; 

Производный класс не может наследовать от нескольких базовых классов, если эти базовые классы имеют конструкторы с идентичной подписью.

Конструкторы и составные классы

Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе StorageBox :

class Label < public: Label(const string& name, const string& address) < m_name = name; m_address = address; >string m_name; string m_address; >; class StorageBox : public Box < public: StorageBox(int width, int length, int height, Label label) : Box(width, length, height), m_label(label)<>private: Label m_label; >; int main()< // passing a named Label Label label1< "some_name", "some_address" >; StorageBox sb1(1, 2, 3, label1); // passing a temporary label StorageBox sb2(3, 4, 5, Label< "another name", "another address" >); // passing a temporary label as an initializer list StorageBox sb3(1, 2, 3, ); > 

В этом разделе

  • Конструкторы копий и операторы присваивания копированием
  • Конструкторы перемещения и операторы присваивания перемещением
  • Делегирующие конструкторы

Конструкторы

Конструктор инициализирует объект при его создании. У конструктора такое же имя, как и у его класса, а с точки зрения синтаксиса он подобен методу. Но у конструкторов нет возвращаемого типа, указываемого явно. Ниже приведена общая форма конструктора:

доступ имя_класса(список_параметров) < // тело конструктора >

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

Каждый класс C# снабжается конструктором по умолчанию, который при необходимости может быть переопределен. По определению такой конструктор никогда не принимает аргументов. После размещения нового объекта в памяти конструктор по умолчанию гарантирует установку всех полей в соответствующие стандартные значения. Если вы не удовлетворены такими присваиваниями по умолчанию, можете переопределить конструктор по умолчанию в соответствии со своими нуждами.

Конструктор также может принимать один или несколько параметров. В конструктор параметры вводятся таким же образом, как и в метод. Для этого достаточно объявить их в скобках после имени конструктора.

Давайте рассмотрим применение конструкторов на примере:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < class MyClass < public string Name; public byte Age; // Создаем параметрический конструктор public MyClass(string s, byte b) < Name = s; Age = b; >public void reWrite() < Console.WriteLine("Имя: \nВозраст: ", Name, Age); > > class Program < static void Main(string[] args) < MyClass ex = new MyClass("Alexandr", 26); ex.reWrite(); Console.ReadLine(); >> >

Как видите, в данном примере поля экземпляра класса ex инициализируются по умолчанию с помощью конструктора.

Зачем нужен конструктор по умолчанию c

то есть компилятор не сможет создать объект без конструктора, это как-бы необходимый шаг, даже если конструктор пустой

(3) Если программист не определил конструктор в классе - то компилятор создаст конструктор (без параметров) для создания объектов этого класса.
Но мне кажется, вопрос в чём-то другом?

(4)вопрос зачем он это сделает

(5) А иначе возникнут проблемы с созданием
class B < int i; >
class D: B < int j; D() < j=0; >> /* при создании D надо вызвать конструктор для B - а его нет */
class C < B x; C() <>> /* x надо ведь как-то создать */

Но если программист определил свой конструктор - то будет использоваться тот, что определил программист.

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

(7) Да, собственно, и программа на С++ нужна тоже компилятору. Если нет программы на С++ - нахрена тогда нужен компилятор? 🙂

(7) .
Вообще-то всё это нужно программисту. В том числе и конструктор по умолчанию.

+(10) Ну или
class B < private: B() <>>:
class D: public B < int i; >:
int main()
< D x; >

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

(8) У _какого_? См. (10) - как работает конструктор по умолчанию для D.

(12) Да нет. Надо знать этот механизм (а в C++ он не так уж и прост; впрочем, а что в C++ сделано просто?)

(14)Сложность c++ в том, что этот язык дает полную свободу программисту и в тоже время это заставлет его делать все вручную. Хотя есть библиотеки STL и другие библиотеки.

(15) Используй другой язык 🙂

разрыв мозга просто.
"я теперь понял, что они не пересекаются, но я не могу понять почему они не пересекаются"

(5) Чтобы можно было создать экземпляр класса. хоть бы и пустой
(14) Самый логичный язык, кмк. Ну, Ява еще. В ней код особенно элегантно выглядит

(19) Логичный. Один из наиболее точно и строго определённых языков (а среди промышленных языков - как бы не самый). Но проблема в том, что эту логику надо знать. Ну и эта логика (впрочем, как и любая другая) - не слишком наглядна (на любом форуме по C++ полно вопросов "а как эта хрень должна работать?", ответ на которые всегда находится и обосновывается - но не всегда очевиден).

фигня, Си уже страдал фигней, а в плюсах вместо полировки качества языка - фигни только добавили и стало просто жёпа
вот такой он Си++ неудобный, но в свое время был наилучшим, всему свое время есть, приходит и уходит

(21) что неудобного ?
(0)тебе объяснить слово "по умолчанию"? или объяснить почему совсем без конструктора нельзя?
(23)
"Верблюда спросили:
- Почему у тебя спина кривая?
- А что у меня прямое? - ответил верблюд"
(25)"смотри, как они лошадку изуродовали. "©
(25)Вот именно.
Положено так по сценарию.
🙂

(24) Если не утрировать - то действительно имеет место быть набор неудобств. Как следствие сложности самих языковых механизмов. Столь гибкое множественное наследие вкупе с управлением областями видимости нечасто встретишь, а если на это накладывается мощь C++ных шаблонов (которые, вообще говоря, как бы не Тьюринг-полные) - то начинается сплошное веселье.

(28)Всегда ненавидел разбирать эти библиотеки..Там как в вики - начнешь искать отчество Ленина, очнешься что читаешь про овечку Долли.

+(28) Да и хотя бы те же конструкторы - они ведь в C++ отличаются от всех прочих (Java, Delphi, C# и т.д.).

(30)В шарпе вроде также все.

(29) "Кормил ребёнка. Положил соску на горячую плиту. Она прилипла. Набрал в поисковике "Как отодрать соску". " (c) молодой, но уже бородатый анекдот

синтаксис может чуть другой
(0) ты когда родился у родителей не спросил почему у тебя 5 пальце на одной руке а не 4 ?
(21) чушь несешь!

(31) Не совсем. Конструктор в C++ работает с ещё не до конца созданным объектом - отсюда и некоторые ограничения. В новом стандарте это вроде бы подправили. Но (как обычно:-) сохранив "тонкую логику".

(0) вообще иди читай что такое есть конструктор в ООП.
(37) А чем ему это поможет?
(Честно говоря, я так и не понял, что именно хотел узнать ТС).

(38)Да его жаба давит от пустой процедуры которая ничего не делает, но без которой ниче не работает:-)

(39) Он не понимает, что она - НЕ пустая (в общем случае). И НЕ функция 🙂
(Насчёт последнего - поэтому ему и мало поможет общее понятие конструктора, поскольку в разных языках конструктор трактуется по разному).

(40)В плюсах еси што вообще нет функций:-)..Есть void, есть не void
+(41)Вернее нет процедур, да:-)..
Вброс: с++ не нужен, если есть хаскель и немерле.
(21) Я что-то пропустил. А когда С++ успел так сильно поменяться?

(36) В смысле "не до конца"? Кажется, память уже выделена под типы, которые ты заявил в качестве переменных-членов класса. Т.е. с ними уже можно работать

(41) Есть конструкторы 🙂 Они в C++ - не функции (ну или не совсем функции) 🙂

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

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

си++ очень мощен, спору нет, но. (21) никто не отменял!

Конструктор нужен всяко по стандартам языка для создания обьекта. Если конструктора нет - то и нет объекта.

(20)
известная "точность" - что будет в переменной после вычисления
int i=5;i=++i + ++i;
делает он это, чтобы инициализировать переменные объекта. например, переменные объекты.

(0) Объективных причин нет и это скорее всего компиляторозависимая фича.
(6) Такое на этапе компиляции прекрасно разруливается, можно сделать конструктор private и компилятор сам расскажет про все неявные места его вызова или попытки взять адрес.

+(53) Конечно, если в теле есть объекты у которых есть свои конструкторы, то тогда надо сформировать код, который их в правильном порядке вызовет. Тогда генерация конструктора необходима и неизбежна.
Более того компилятор вставит этот код даже в созданный явно конструктор. Переопределить это поведение можно записав вызов конструкторов через двоеточие после декларации конструктора,но до его тела (до открывающей фигурной скобки)

(44) C++0x посмотри, там кусок boost'а внесен в стандарт, введены автоматические типы переменных и еще куча всего.

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

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