Конструктор – метод класса, автоматически применяемый к каждому экземпляру (объекту) класса перед первым использованием (в случае динамического выделения памяти – после успешного выполнения операции new).
Освобождение ресурсов, захваченных в конструкторе класса либо на протяжении времени жизни соответствующего экземпляра, осуществляет деструктор.
Выполнение любого конструктора состоит из двух фаз:
Конструктор не может определяться со спецификатором const. Константность и объекта устанавливается по завершении работы конструктора и снимается перед вызовом деструктора.
При объявлении структуры, если не писать явно, то всегда есть конструктор по умолчанию. Его можно переопределить задав в нем некоторые значения объектов данного класса.
В приведенном ниже примере объявляется структура дроби с двумя закрытыми переменными. По умолчанию структура в конструкторе инициализируется. Значения числителя и знаменателя можно получить/изменить с помощью функций геттера/сеттера:
struct Fraction {
Fraction() { //конструктор по умолчанию
// инициализация
_numerator = 0;
_denominator = 1;
}
int getNumerator() { return _numerator; }
void setNumerator(int value) { _numerator = value; }
int getDenominator() { return _denominator; }
void setDenominator(int value) { _denominator = value; }
double getValue() {
if (_denominator != 0) {
return (double)_numerator / _denominator;
} else {
return .0;
}
}
private:
int _numerator;
int _denominator;
};
int main() {
Sample s; // статическое создание
Sample *sp = new Sample(); // динамичесое создание
return 0;
}
В конструктор как и в любую другую функцию можно передавать начальные значения:
struct Fraction {
Fraction() { //конструктор по умолчанию
// инициализация
_numerator = 0;
_denominator = 1;
}
Fraction(int numerator, int denominator) { //конструктор с параметрами
// инициализация
_numerator = numerator;
_denominator = denominator;
}
int getNumerator() { return _numerator; }
void setNumerator(int value) { _numerator = value; }
int getDenominator() { return _denominator; }
void setDenominator(int value) { _denominator = value; }
double getValue() {
if (_denominator != 0) {
return (double)numerator / _denominator;
} else {
return .0;
}
}
private:
int numerator;
int _denominator;
};
int main() {
Sample s(10, 20); // статическое создание
Sample *sp = new Sample(30, 40); // динамичесое создание
return 0;
}
Но также, некоторые значения могут задваться по умолчанию, тогда пример выше можно свести к одному конструктору:
struct Fraction {
Fraction(int numerator = 0, int denominator = 1) { //конструктор с параметрами
// инициализация
_numerator = _numerator;
_denominator = denominator;
}
int getNumerator() { return _numerator; }
void setNumerator(int value) { _numerator = value; }
int getDenominator() { return _denominator; }
void setDenominator(int value) { _denominator = value; }
double getValue() {
if (_denominator != 0) {
return (double)numerator / _denominator;
} else {
return .0;
}
}
private:
int _numerator;
int _denominator;
};
int main() {
Sample s(denominator = 20); // статическое создание c (0, 20)
Sample *sp = new Sample(); // динамичесое создание с (0, 1)
return 0;
}
Для конструктора возможны следующие синтаксические конструкции:
struct Sample {
// прямая инциализация переменной.
Sample(int value) : _value(value) { }
private:
int _value;
};
struct Sample2 {
// прямая инциализация переменной.
Sample(int value1 = 10, int value2 = 20) : _value1(value1), _value2(value) { }
private:
int _value1;
int _value2;
};
int main() {
// следующие объявления эквивалентны:
Sample s1(10);
Sample s2 = Sample(10);
Sample s3 = 10;
// массивы объектов класса определяются аналогично массивам
// объектов базовых типов
// для конструктора с одним аргументом
Sample array1[] = { 10, -5, 0, 127 };
// для конструктора сокращенной формы нет
Sample2 array2[5] = {
Sample2(10, 0.1),
Sample2(-5, -3.6),
Sample2(0, 0.0),
Sample2()
};
}
Надо отметить, что при простом объявлении структуры/класса:
struct A { } ;
неявным образом создаются следующие функции:
A()
- конструктор по умолчаниюA(const A& a)
- конструктор копированияoperator=(const A& a)
- копирующий операторA(const&& a)
- конструктор перемещенияoperator=(const A&& a)
- оператор перемещения~A()
- деструкторО функциях 2-5 речь пойдет позже, а вот о деструктор можно рассмотреть сейчас.
Деструктор – не принимающий параметров и не возвращающий результат функция класса, автоматически вызываемая при выходе объекта из области видимости и применении к указателю на объект класса операции delete. Деструктор всегда имеет имя класса/структуры с ~
перед ним.
struct Sample {
// ...
~Sample();
};
Деструктор не вызывается при выходе из области видимости ссылки или указателя на объект.
Задачи деструктора:
struct Another
{
Another(int nID)
{
cout << "Constructing Another " << nID << '\n';
_nID = nID;
}
~Another()
{
cout << "Destructing Another " << _nID << '\n';
}
int getID() { return _nID; }
private:
int _nID;
};
int main()
{
// Выделяем объект класса Another из стека
Another object(1);
cout << object.getID() << '\n';
// Выделяем объект класса Another динамически из кучи
Another *pObject = new Another(2);
cout << pObject->getID() << '\n';
delete pObject; // произойдет неявный вызов дестркутора pObject
return 0;
} // объект object выходит из области видимости и вызовется деструктор
Общее правило - не вызываем деструктор явно.
Однако, есть случаи, когда вызывать деструтор явно бывает полезно. Потребность в явном вызове деструктора обычно связана с необходимостью уничтожить динамически размещенный объект без освобождения памяти. То есть мы даем возможно данный сегмент памяти переиспользовать. Достигается это также с помощью расширения оператора new указывая где нужно разметить данный объект:
char* buf = new char[sizeof(Sample)];
// "размещающий" вариант new
Sample* sample1 = new (buf) Sample(100);
// ...
sample1->~Sample(); // вызов 1
Sample* sample2 = new (buf) Sample(200);
// ...
sample2->~Sample(); // вызов 2
delete[] buf;