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

class A { public: A():a(0),b(0) {} explicit A(int x): a(x), b(0) {} A(int x, int y): a(x), b(y) {} private: int a,b; };

Class A { public: explicit A(int x=0, int y=0): a(x), b(y) {} private: int a,b; };

Есть ли различия? Что лучше использовать?

2 ответа

Vlad from Moscow

Эти два объявления классов не эквивалентны.

Во втором объявлении класса конструктор объявлен со спецификатором функции explicit , а это ограничивает применение этого конструктора в различных ситуациях.

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

То есть первое объявление предоставляет больше возможностей по использованию класса.

Рассмотрите следующую демонстрационную программу

#include struct A { explicit A(int x = 0, int y = 0) : x(x), y(y) {} int x; int y; }; struct B { B() : x(0), y(0) {} explicit B(int x): x(x), y(0) {} B(int x, int y): x(x), y(y) {} int x; int y; }; void f(const A &a) { std::cout << "a.x = " << a.x << ", a.y = " << a.y << std::endl; } void g(const B &b) { std::cout << "b.x = " << b.x << ", b.y = " << b.y << std::endl; } int main() { // f({}); // f({ 1, 2 }); g({}); g({ 1, 2 }); }

Ее вывод на консоль:

B.x = 0, b.y = 0 b.x = 1, b.y = 2

В этой программе два вызова функции f закомментированы, так как если их раскомментировать, то компилятор выдаст сообщение об ошибке.

Другое важное отличии состоит в том, что один класс имеет всего лишь один конструктор с заданной сигнатурой, а другой класс имеет три конструктора с различными сигнатурами.

Рассмотрите еще один демонстрационный пример

Struct A { explicit A(int x = 0, int y = 0) : x(x), y(y) {} int x; int y; }; struct B { B() : x(0), y(0) {} explicit B(int x): x(x), y(0) {} B(int x, int y): x(x), y(y) {} int x; int y; }; struct C { //friend A::A(); friend B::B(); }; int main() { }

Здесь в классе C вы можете объявить конструктор по умолчанию класса B в качестве друга класса С. Однако вы не можете сделать то же самое с конструктором по умолчанию класса A , чтобы объявить его другом класса C , так как конструктор по умолчанию в классе A имеет другую сигнатуру.

Вам уже придется писать

Struct C { friend A::A(int, int); };

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

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

Если рассматривать не конструкторы, а функции, то разница имеется еще более существенная.

Аргументы по умолчанию не влияют на тип функции. Поэтому, например, если вы объявили функцию как

Void f(int, int = 0);

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

F(value);

тем не менее ее тип void(int, int) . А это в свою очередь означает, что вы не можете, например, написать

Void h(void f(int)) { f(10); } void f(int x, int y = 0) { std::cout << "x = " << x << ", y = " << y << std::endl; } // h(f);

так как параметр функции h имеет тип void(int) ., а у функции, используемой в качестве аргумента, тип void(int, int)

Если же вы объявите две функции вместо одной

Void h(void f(int)) { f(10); } void f(int x) { std::cout << "x = " << x << std::endl; } void f(int x, int y) { std::cout << "x = " << x << ", y = " << y << std::endl; }

то данный вызов

будет корректным, так как имеется функция с одним параметром.

IxSci

Различия уже объяснил @Vlad from Moscow, я лишь предложу, из двух вариантов в вопросе, третий вариант:

Class A { public: A():A(0, 0) {} explicit A(int x): A(x, 0) {} A(int x, int y): a{x}, b{y} {} private: int a,b; };

На мой взгляд именно этот вариант является лучшим, т.к. он имеет явный конструктор с одним аргументом, что является хорошей практикой и уберегает от некоторых ошибок. С другой стороны, explicit для конструкторов, у которых больше или меньше одного аргумента, на мой взгляд, является лишним. Т.к. случайно создать объект из более чем одного аргумента проблематично, а именно за этим мы и приписали explicit у одноаргументнова конструктора - защита от случайных ошибок.

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

Если нет явным образом опредёленных конструкторов в классе , то компилятор использует конструктор по умолчанию, опредёленный неявным способом, который аналогичен «чистому» [уточнить ] конструктору по умолчанию. Поэтому, класс не гарантирует наличия конструктора по умолчанию (то есть когда программист явным образом определяет только конструктор, который не по умолчанию). Некоторые программисты явным образом задают конструктор по умолчанию по привычке, чтобы не забыть в дальнейшем, но это не обязательно. В C++ только массивы имеют конструкторы по умолчанию, которые создают каждый элемент при помощи конструктора по умолчанию для их типа.

В C++ и Java если производный класс не вызывает явным образом конструктор базового класса (в C++ в списке инициализации, в Java используя super() в первой строчке), то конструктор по умолчанию вызывается неявно. Если базовый класс не имеет конструктора по умолчанию, то это считается ошибкой. В C++ если поле экземпляра класса явным образом не инициализировано в списке, то вызывается конструктор по умолчанию для инициализации этого поля. Если такой тип не имеет конструктора по умолчанию, то это также считается ошибкой.


Wikimedia Foundation . 2010 .

Смотреть что такое "Конструктор по умолчанию" в других словарях:

    Конструктор - получить на Академике рабочий купон на скидку Ашан или выгодно конструктор купить с бесплатной доставкой на распродаже в Ашан

    конструктор по умолчанию - Конструктор, создаваемый компилятором при отсутствии конструктора класса. [ГОСТ Р 54456 2011] Тематики телевидение, радиовещание, видео EN default constructor … Справочник технического переводчика

    У этого термина существуют и другие значения, см. Конструктор. В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) специальный блок инструкций, вызываемый при создании объекта.… … Википедия

    В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) специальный блок инструкций, вызываемый при создании объекта, причём или при его объявлении (располагаясь в стеке или в статической… … Википедия

    Конструктором копирования (в англоязычной литературе используется термин copy constructor) называется специальный конструктор в языке программирования C++, применяемый для создания нового объекта как копии уже существующего. Такой конструктор… … Википедия

    В компьютерном программировании нуль арным конструктором (в англ. языке используется термин nullary constructor) называют конструктор, не принимающий аргументы. Содержание 1 Объектно ориентированные конструкторы 1.1 … Википедия

    C++0x будущая версия стандарта языка C++, вместо ныне существующего ISO/IEC 14882:2003. Новый стандарт будет включать дополнения в ядре языка и расширение STL, включая большую часть TR1 кроме, вероятно, библиотеки специальных… … Википедия

    Стандартная библиотека языка программирования C++ fstream iomanip ios iostream sstream Стандартная библиотека шаблонов … Википедия

    Стандартная библиотека шаблонов (STL) (англ. Standard Template Library) набор согласованных обобщенных алгоритмов, контейнеров, средств доступа к их содержимому и различных вспомогательных функций. Стандартная библиотека шаблонов до включения в… … Википедия

    C++11 или ISO/IEC 14882:2011 (в процессе работы над стандартом носил условное наименование C++0x) новая версия стандарта языка C++, вместо ранее действовавшего ISO/IEC 14882:2003. Новый стандарт включает дополнения в ядре… … Википедия

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

Вот простой пример, как вы ответите на следующий вопрос: сколько значимых типов из. NET Framework содержит конструкторы по умолчанию? Интуитивным ответом кажется "все ", и будете не правы, поскольку на самом деле, ни один из значимых типов. NET Framework не содержит конструктора по умолчанию .

Работа с массивами – это не единственное место, когда существующий конструктор значимого типа вызван не будет. Следующая таблица дает понять, когда такой конструктор будет вызван, а когда нет.
// Вызывается var cvt = new CustomValueType(); // Вызывается var cvt = Activator.CreateInstance(typeof(CustomValueType)); // Не вызывется var cvt = default(CustomValueType); static T CreateAsDefault() { return default(T); } // Не вызывается CustomValueType cvt = CreateAsDefault(); static T CreateWithNew() where T: new() { return new T(); } // Не вызывается!! CustomValueType cvt = CreateWithNew(); // Не вызывается var array = new CustomValueType;

Хочу обратить внимание на рассогласованность поведения в следующем случае: известно, что создания экземпляра обобщенного (generic) параметра происходит с помощью Activator.CreateInstance , именно поэтому при возникновении исключения в конструкторе пользователь обобщенного метода получит его не в чистом виде, а в виде TargetInvocationException . Однако при создании экземпляра типа CustomValueType с помощью Activator .CreateInstance наш конструктор по умолчанию будет вызван, а при вызове метода CreateWithNew и создания экземпляра значимого типа с помощью new T () – нет.

Заключение

Итак, мы выяснили следующее:
  1. Конструктором по умолчанию в языке C# называется инструкция обнуления значения объекта.
  2. С точки зрения CLR конструкторы по умолчанию существуют и язык C# даже умеет их вызывать.
  3. Язык C# не позволяет создавать пользовательские конструкторы по умолчанию для структур, поскольку это привело бы к падению производительности при работе с массивами и существенной путанице.
  4. Работая на языках, поддерживающих создание конструкторов по умолчанию, их объявлять все равно не стоит по тем же причинам, по которым они запрещены в большинстве языков платформы.NET.
  5. Значимые типы не так просты как кажется: помимо проблем с изменяемостью (мутабельностью) у значимых типов даже с конструкторами по умолчанию и то не все просто.

Функции-члены класса

#include

// содержит функции ввода-вывода

#include

// содержит функцию CharToOem

#include

// содержит математические функции

class Dot

// класс точки

// закрытые члены класса

char name ;

// имя точки

double x , y ;

// координаты точки

// открытые члены класса public :

// конструктор с параметрами

Dot (char Name , double X , double Y) { name = Name ; x = X ; y = Y ; }

void Print () ;

Здесь значение, переданное в конструктор при объявлении объекта A , используется для инициализации закрытых членов name , x и y этого объекта.

Фактически синтаксис передачи аргумента конструктору с параметрами является сокращенной формой записи следующего выражения:

Dot A = Dot ("A" , 3 , 4) ;

Однако практически всегда используется сокращенная форма синтаксиса, приведенная в примере.

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

Правила для конструкторов

Приведем правила, которые существуют для конструкторов:

конструктор класса вызывается всякий раз, когда создается объект его класса;

конструктор обычно инициализирует данные-члены класса и резервирует память для динамических членов;

конструктор имеет то же имя, что и класс, членом которого он является; для конструктора не указывается тип возвращаемого значения; конструктор не может возвращать значение; конструктор не наследуется;

класс может иметь несколько перегруженных конструкторов;

конструктор не может быть объявлен с модификатором const , volatile , static или virtual ;

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

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

Для деструкторов существуют следующие правила: деструктор вызывается при удалении объекта;

деструктор обычно выполняет работу по освобождению памяти, занятой объектом;

деструктор имеет то же имя, что и класс, которому он принадлежит, с предшествующим символом ~ ; деструктор не может иметь параметров; деструктор не может возвращать значение; деструктор не наследуется; класс не может иметь более одного деструктора;

деструктор не может быть объявлен с модификатором const , volatile или static ;

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

Подчеркнем еще раз: конструкторы и деструкторы в C++ вызываются автоматически, что гаран-

тирует правильное создание и удаление объектов класса.

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

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

ConstrName (parl , par2) : mem1 (parl) , mem2 (par2) , … { …}

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

class Dot

char name ;

// имя точки

double x , y ;

// координаты точки

public:

// конструктор со списком инициализации

Dot (char Name) : name (Name) , x (0) , y (0) { }

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

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

C++ определяет два специальных вида конструкторов: конструктор по умолчанию, о котором мы упоминали выше, и конструктор копирования. Конструктор по умолчанию не имеет параметров (или все его параметры должны иметь значения по умолчанию) и вызывается при создании объекта, которому не заданы аргументы. Следует избегать двусмысленности при вызове конструкторов. В приведенном ниже примере два конструктора по умолчанию являются двусмысленными:

class Т

public:

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

Т (int i = 0) ;

// конструктор с одним необязательным параметром

// может быть использован как конструктор по умолчанию

void main ()

// Использует конструктор T::T (int)

// Не верно; неоднозначность вызова Т::Т () или Т::Т (int = 0)

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

Конструкторы копирования

Конструктор копирования создает объект класса, копируя при этом данные из уже существующего объекта данного класса. В связи с этим он имеет в качестве единственного параметра константную ссылку на объект класса (const Т& ) или просто ссылку на объект класса (Т& ). Использование первого предпочтительнее, так как последний не позволяет копировать константные объекты.

рования по умолчанию. В C++ различают поверхностное и глубинное копирование данных.

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

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

Если в классе не определен конструктор, компилятор пытается сгенерировать собственный конструктор по умолчанию и, если нужно, собственный конструктор копирования. Эти сгенерированные компилятором конструкторы рассматриваются как открытые функции-члены. Подчеркнем, что компилятор генерирует эти конструкторы, если в классе не определен никакой другой конструктор.

Приведём пример использования конструктора копирования:

class Dot

// класс точки

// закрытые члены класса

char name ;

// имя точки

double x , y ;

// координаты точки

public:

// открытые члены класса

// конструкторы с параметрами

Dot (char Name , double X , double Y)

{ name = Name ; x = X ; y = Y ; }

Dot (char Name) : name(Name) , x (0) , y (0)

Dot (char Name , const Dot& A)

{ name = Name ; x = A.x ; y = A.y ; }

// конструктор копирования

Dot (const Dot& A)

{ name = (char ) 226 ; x = A.x ; y = A.y ; }

void Print () ;

// выводит на экран имя и координаты текущей точки

void main ()

Dot A ("A", 3 , 4) ;

// вызов конструктора Dot (char Name , double X , double Y)

// вызов конструктора Dot (char Name)

// выводит на экран:

Координаты точки т :

// вызов конструктора копирования Dot (const Dot& A)

// выводит на экран:

Координаты точки т :

Dot E ("E", A) ;

// вызов конструктора Dot (char Name , const Dot& A)

// выводит на экран:

Координаты точки E:

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

Дело в том, что:

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

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

2. При создании конструкторов класса BClass, который наследуется от другого класса, компилятор требует, чтобы первой строкой конструктора был вызов другого конструктора (унаследованного или в этом классе).

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

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

Public class AClass1 { }

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

Это эквивалентно такому определению:

Public class AClass1 { public AClass1() { } }

Теперь посмотрим на BClass1:

Public class BClass1 extends AClass1 { }

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

Public class BClass1 extends AClass1 { public BClass1() { super(); } }

В Вашем случае создается класс БЕЗ конструктора по умолчанию:

Public AClass { public AClass(int i) { } }

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

AClass a = new AClass(); // не работает

нужно что-то вроде

AClass a = new AClass(1);

Соответственно, любой конструктор BClass будет требовать вызова какого-либо конструктора AClass или BClass. При таком описании компилятор будет ругаться:

Public BClass extends AClass { }

Потому что будет попытка вызова конструкора по умолчанию класса AClass, который не определен:

Public BClass extends AClass { public BClass() { super(); // ошибка; в классе AClass нет такого конструктора } }

Тем не менее, можно создать класс BClass с конструктором по умолчанию, задав какое-то значение для конструктора AClass:

Public class BClass extends AClass { public BClass() { super(1); } }

Это будет компилироваться.