Инициализация в современном С++
Комбинирование конструкторов объектов
Cсылочный тип данных_3 rvalue- ссылки
Семантика переноса (Move semantics)
Семантика переноса (Move semantics). Пример
Семантика переноса (Move semantics)
delete - для запрещения ненужных методов
default - для автоматической генерации специальных методов класса
Правила автоматической генерации специальных методов класса
362.00K
Категория: ПрограммированиеПрограммирование

Специальные методы класса

1.

Специальные методы класса
Конструктор – метод класса, который
- имеет имя, в точности совпадающее с именем самого
класса;
- не имеет типа возвращаемого значения;
- всегда вызывается при создании объекта (сразу после
отведения памяти под объект в соответствии с его
описанием).
Деструктор – метод класса, который
- имеет имя, совпадающее с именем класса, перед
первым символом которого приписывается символ ~ ;
- не имеет типа возвращаемого значения и параметров;
- всегда вызывается при уничтожении объекта (перед
освобождением памяти, отведенной под объект).
1

2.

Специальные методы класса
class A { ......
public:
A ( );
// конструктор умолчания
A (A & y); // A (const A & y); конструктор копирования (КК)
[explicit] A (int x); // конструктор преобразования; explicit запрещает
// компилятору неявное преобразование int в А
A (int x, int y);
// A (int x = 0, int y = 0); // заменяет 1-ый, 3-ий и 4-ый
// конструкторы
~A (); // деструктор
......
};
Int main () {
A a1, a2 (10), a3 = a2;
A a4 = 5, a5 = A(7); // Err!, т.к. временный объект не может быть
// параметром для неконстантной ссылки в КК
// О.К., если будет
A (const A & y)
A * a6 = new A (1);
}
2

3.

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

4.

Указатель this
Иногда для реализации того или иного метода возникает
необходимость иметь указатель на «свой» объект, от имени
которого производится вызов данного метода.
В C++ введено ключевое слово this, обозначающее
«указатель на себя», которое можно трактовать как неявный
параметр любого метода класса:
<имя класса> * const this;
*this – сам объект.
Таким образом, любой метод класса имеет на один (первый)
параметр больше, чем указано явно.
this, участвующий в описании функции, перегружающей
операцию, всегда указывает на самый левый (в выражении с
этой операцией) операнд операции.
В реальности поле this не существует (не расходуется
память), и при сборке программы вместо this подставляется
соответствующий адрес объекта.
4

5.

Класс Box
class Box {
int l;
// length – длина
int w;
// width – ширина
int h;
// height – высота
public:
int volume () const { return l * w * h ; }
Box (int a, int b, int c ) { l = a; w = b; h = c; }
Box (int s) { l = w = h = s; }
Box ( ) { w = h = 1; l = 2; }
int get_l ( ) const { return l; }
int get_w ( ) const { return w; }
int get_h ( ) const { return h; }
};
Автоматически сгенерированные КК и операция присваивания:
Box (const Box & a) { l = a.l; w = a.w; h = a.h; }
Box& operator= (const Box & a) { l = a.l; w = a.w; h = a.h; return * this;}
Конструктор копирования и операцию присваивания можно
переопределить.
5

6.

Инициализация членов-данных с помощью списка
инициализации при описании конструкторов
Пример (композиция объектов):
class Point {
int x;
int y;
public:
Point ( );
Point ( int, int );
...
};
Z * a = new Z (1);
delete z;
class Z {
Point p;
int i;
public:
Z ( int с ) { i = c; }
...
};
// Point ( ); Z(1);
// ~Z(); ~Point();
Инициализация члена-данного (подобъекта) р конструктором с двумя
параметрами с помощью списка инициализации:
Z :: Z ( int c ) : p (1, 2) { i = c; } или
Z :: Z ( int c ) : p (1, 2), i (c) { }
6

7. Инициализация в современном С++

Помимо инициализации в конструкторах или с помощью списка
инициализации возможно также инициализировать члены-данные
класса и в области их объявления в классе.
Пример:
struct B {
int t {1};
B (int k ): t(k) { }
B(){ }
};
struct A {
int n = 14;
B b = B(3);
int m [4] = {1,2,3,4};
A(int x=0, int y=0) : n(x), b(y) { }
static const int cc = 8; // только для
}; // интегральных типов стат. констант
int main() {
A a1{7,9}, a2;
std::cout << a1.n << a1.b.t << a2.n << a1.m[2] << std::endl // 7903
return 0;
}
Наряду с () для задания инициализирующих значений можно
использовать {}, как универсальный способ инициализации.
7

8. Комбинирование конструкторов объектов

Можно вызывать одни конструкторы класса (так называемые
делегирующие конструкторы) из других, что в целом позволяет
избежать дублирования кода (с С++11).
Пример:
class A {
int n;
public:
A (int x) : n (x) { }
A ( ) : A (14) { }
};
Замечание: Если до конца проработал хотя бы один
делегирующий конструктор, его объект уже считается полностью
созданным.
Однако, объекты производного класса начнут конструироваться
только после выполнения всех конструкторов (основного и его
делегирующих) базовых классов.
8

9.

Cсылочный тип данных_2
Константные ссылки
Использование ссылок на константу– формальных параметров
функций (для эффективности реализации в случае объектов классов).
Инициализация параметра – ссылки на константу происходит во
время передачи фактического параметра, который, в частности, может
быть временным объектом, сформированным компилятором для
фактического параметра-константы.
Пример:
struct A {
int a;
A( int t = 0) { a = t; }
};
int f (const int & n, const A & ob) {
return n+ob.a;
}
int main () {
cout << f (3, 5) << endl;
...
}
9

10.

Константные ссылки - Пример 1
struct Cl {
int a;
Cl ( int t = 0) { a = t; }
~Cl() { a = 0; cout << "Destr\n";}
};
const Cl & gg (const Cl & ob) {
return ob;
}
int main () {
На печать:
const Cl & n = gg(3);
Destr
const Cl * p = &gg(5);
Destr
cout << n.a << endl;
0 ?????
cout << p → a << endl;
0 ?????
return 0;
}
Но!!! в строках, выводящих на экран, мы работаем с разрушенным
объектом и имеем ситуацию undefined behavior.
10

11.

Константные ссылки - Пример 2
struct Cl {
int a;
Cl ( int t = 0) { a = t; }
~Cl() { a = 0; cout << "Destr\n";}
};
const Cl & gg (const Cl & ob) {
return ob;
}
int main () {
На печать:
const Cl & n = Cl(3);
const Cl * p = &gg(5);
Destr
cout << n.a << endl;
3
cout << p → a << endl;
0 - ??? - undefined behavior.
return 0;
Destr
}
11

12.

Временные объекты (ВО)
ВО создаются в рамках выражений, где их можно модифицировать
(применять неконстантные методы, менять значения членов-данных).
«Живут» ВО до окончания вычисления соответствующих выражений.
НО! Если инициализировать ссылку на константу ВО-ом (передавать ВО
в качестве фактического параметра для формального параметра–
ссылки на константу), время его жизни продлевается до конца жизни
соответствующей ссылочной переменной (до конца тела функции).
НЕЛЬЗЯ инициализировать неконстантную ссылку ВО-ом !
Пример:
struct A {
A (int);
A (const A &);
};
… const A & r = A (1);
// если здесь и в КК убрать const,
A a1 = A (2);
// все эти конструкции будут
A a2 = 3; …
// ошибочными
Важно! Компилятор ВСЕГДА сначала проверяет синтаксическую и
семантическую (контекстные условия) правильность, а затем
оптимизирует!!!
12

13.

Порядок вызова конструкторов и деструкторов
При вызове конструктора класса выполняются:
1.
конструкторы базовых классов (если есть наследование),
2.
конструкторы всех вложенных объектов в порядке их
описания в классе,
3.
собственный конструктор (при его вызове все поля класса уже
проинициализированы, следовательно, их можно использовать).
Деструкторы выполняются в обратном порядке:
1.
собственный деструктор (при этом поля класса ещё не очищены,
следовательно, доступны для использования),
2.
автоматически вызываются деструкторы для всех вложенных
объектов в порядке, обратном порядку их описания в классе,
3.
деструкторы базовых классов (если есть наследование).
13

14.

Вызов конструктора копирования
*
*
явно,
в случае:
Box a (1, 2, 3);
Box b = a; // a – параметр конструктора копирования,
*
в случае: Box c = Box (3, 4, 5);
// сначала создается временный объект и вызывается
// обычный конструктор, а затем работает конструктор
// копирования при создании объекта с; если компилятор
// оптимизирующий, вызывается только обычный
// конструктор с указанными параметрами;
*
при передаче параметров функции по значению (при создании
локального объекта для его инициализации);
*
при возвращении результата работы функции в виде объекта,
*
при генерации исключения-объекта.
14

15.

Вызов других конструкторов
явно,
при создании объекта (при обработке описания объекта),
при создании объекта в динамической памяти (по new), при этом
сначала в «куче» отводится необходимая память, а затем
работает соответствующий конструктор,
при композиции объектов наряду с собственным конструктором
вызывается конструктор объекта – члена класса,
при создании объекта производного класса также вызывается
конструктор и базового класса,
при автоматическом приведении типа с помощью конструктора
преобразования.
15

16.

Вызов деструктора
* явно,
*
при свертке стека - при выходе из блока описания объекта, в
частности
при обработке исключений, завершении работы функции;
*
при уничтожении временных объектов - сразу, как только
завершается
конструкция, в которой они использовались;
*
при выполнении операции delete для указателя на объект
(инициализация указателя - с помощью операции new), при этом
сначала работает деструктор, а затем освобождается память.
*
при завершении работы программы при удалении
глобальных/статических объектов.
Конструкторы вызываются в порядке определения объектов в
блоке. При выходе из блока для всех локальных объектов
вызываются деструкторы, в порядке, противоположном порядку
выполнения конструкторов.
16

17.

Cсылки – члены класса
Ссылки могут быть членами-данными класса.
Инициализация поля-ссылки класса обязательно происходит через
список инициализации конструктора, вызываемого при создании объекта
или при явной инициализации ссылки при ее описании.
Пример:
class A {
int x;
public:
int & r;
A( ) : r (x) { x = 3; }
A(const A & rhs) : r(x), x(rhs.x) {} // !!! надо описать явно, чтобы r
// имела другой адрес (не &rhs) !!!
A & operator= (const A& а); // !!! надо писать явно, ссылке можно
// присваивается только значение по ссылке а.r
...
};
int main () { A a; ... }
17

18.

Неплоские классы
Класс принято называть неплоским, если он содержит в
качестве полей-данных указатели или ссылки.
Простое (плоское) копирование или присваивание
объектов таких классов может привести к
нежелательным результатам.
Пример:
struct A {
int k = 7;
int & n (k); };
int main () {
A a, b = a;
a.n = a.n+1;
b.n = 99;
cout << a.n << “ ” << b.n << endl; // 99 99
}
18

19.

Примел неплоского класса Str
class Str {
char * p; // здесь потребуется динамическая память,
int len;
public:
Str (const char * s);
Str (const Str & a);
~ Str ( ) { delete [ ] p; }
Str & operator= (const Str & a);
...
};
Str :: Str (const char * s) {
p = new char [ ( len = strlen (s) ) + 1];
strcpy (p, s);
}
Str :: Str (const Str & a) {
p = new char [ (len = a.len) + 1];
strcpy (p, a.p);
}
19

20.

Пример использования класса string
void f {
Str s1 (“Alice”);
s1
5
Str s2 = s1;
s2
5
Str s3 (“Kate”);
...
s3 = s1;
4
A l
K a
i
c
t
e \0
e
\0
s3
}
{... s1...s2 {...s3...}...s1...s2}
20

21.

Переопределение операции присваивания
Str & Str::operator = (const Str & a) {
if (this == & a)
return * this; // если a = a
delete [ ] p;
p = new char [ (len = a.len) + 1];
strcpy (p, a.p);
return * this;
}
При этом: s1 = s2 ~ s1.operator= (s2);
21

22. Cсылочный тип данных_3 rvalue- ссылки

<тип> && <имя> = <временный объект>;
В С++ можно использовать перегруженные функции для
неконстантных временных объектов, обозначаемых
посредством rvalue-ссылок (с С++11)
Пример:
class A; … A a; …
void f (A & x);
~ f (a);
void f (A && y);
~ f ( A() );

A && rr1 = A();
A && rr2 = a; // Err!
int && n = 1+2;
n++;

22

23. Семантика переноса (Move semantics)

При создании/уничтожении временных объектов неплоских
классов, как правило, требуется выделение-освобождение
динамической памяти, что может отнимать много времени.
Однако, можно оптимизировать работу с временными
объектами неплоских классов, если не освобождать их
динамическую память, а просто перенаправить указатель на нее
в объект, который копирует значение временного объекта
неплоского класса (посредством поверхностного копирования).
При этом после копирования надо обнулить соответствующие
указатели у временного объекта, чтобы его деструктор ее не
зачистил.
Это возможно сделать с помощью перегруженных
конструктора копирования и операции присваивания с
параметрами – rvalue-ссылками. Их называют конструктором
переноса ( move constructor) и операцией переноса ( move
assignment operator).
При этом компилятор сам выбирает нужный метод класса,
если его параметром является временный объект.
23

24. Семантика переноса (Move semantics). Пример

#include <utility>
// также можно <string_view> c 2017
class Str { char * p;
int len;
public:
Str (const char * sss = NULL); // обычный конструктор неплоского класса
Str (const Str &);
// традиционный конструктор копирования
Str (Str && x) {
// move constructor
p = x.p;
x.p = NULL; // !!!
len = x.len; }
Str & operator = (const Str & x); // обычная перегруженная операция =
Str & operator = (Str && x) {
// move assignment operator
std::swap(p, x.p); // !!!
std::swap(len, x.len);
return *this; }
~Str();
// традиционный деструктор неплоского класса
Str operator + ( Str x);
...
24
};

25. Семантика переноса (Move semantics)

Использование rvalue-ссылок в описании методов класса
Str приведет к более эффективной работе, например,
следующих фрагментов программы:
... Str a(″abc″), b(″def″), c;
c = b+a; // Str& operator= (Str &&);
...
void f (Str a ) {
... }

Str b; … f(b); // Str(const Str&)
... f (Str (″dd″) ); ... // Str (Str &&);
25

26. delete - для запрещения ненужных методов

Пример.
struct ex {
ex () {}
ex (const ex &) = delete;
ex & operator=(const ex &) = delete;
void f(int) = delete;
void f(double f) {cout << "ex::f(double)\n"; }
};
int main() {
ex d1, d2;
ex d3 = d1; // Err – вызов запрещенного метода
d1.f(1);
// Err – вызов запрещенного метода
d1.f(1.5);
d1.f(‘a’);
d2 = d1;
return 0;
}
// Err – вызов запрещенного метода
// Err – вызов запрещенного метода
26

27. default - для автоматической генерации специальных методов класса

Пример
class FFF {
float x;
public:
FFF() = default;
FFF(float y) : x(y) { }
FFF (const FFF & ) = default;
FFF & operator= (const FFF &) = default;
FFF (FFF && ) = default;
FFF & operator= (FFF &&) = default;
~FFF() = default;
};
int main () {
FFF a, b(1), c = b;
}
27

28. Правила автоматической генерации специальных методов класса

28
English     Русский Правила