Похожие презентации:
Технологии программирования. Занятие #2
1. Технологии программирования Занятие #2
Касилов Василий Александровичvolgarenok@gmail.com
Telegram: Volgarenok
2. «Классы памяти»
3. Понятия стандарта [ISO/IEC 14882 // [lex.separate] [basic.def.odr]]
Storage-duration classes (Классы времени жизни объектов, «Классы памяти»)Каждый объект в памяти связан с некоторой областью памяти, определяющей время жизни
объекта:
Dynamic storage duration (Динамическая область память) – единственная область
памяти, время жизни объектов в которой управляется пользователем вручную во время
выполнения программы. Существует два вида динамической памяти – Free Store (C++) и
HEAP [она же «Куча»] (C)
Automatic storage duration (Автоматическая область памяти) – область памяти для
локальных объектов (расположенных в теле функций и методов). Время жизни объектов
определяется их областью видимости
Thread-local storage duration (Локальная для потока область памяти) – область
памяти, существующая отдельно для каждого потока выполнения
Static storage duration (Статическая Фиксированная область памяти) – все остальные
объекты
4. Automatic storage duration
5.
Стек (LIFO)6. Static storage duration
7. Строковые литералы (не путать с массивом символов)
READ-ONLYmemory
…
'H'
…
int main() {
const char * greet = "Hi!";
char ya_greet[] = "Hello";
greet
…
'!'
0
…
//greet[0] = 'h';
//Compilation error
ya_greet[0] = 'h';
//Ok
//…
std::cout << greet << "\n";
std::cout << ya_greet << "\n";
}
'i'
…
'H'
…
ya_greet
…
'e'
'l'
'l'
'o'
0
Попытка изменить данные в любой области памяти, отмеченные как const
Undefined behavior
…
8. Глобальные переменные
//x.hppextern int x = 0; //объявление x
//main.cpp
#include <iostream>
int x = 10;
int main() {
int a = 0;
std::cin >> a;
if (std::cin.good()) {
std::cout << a << "\n";
} else {
std::cout << x << "\n";
}
}
//main.cpp
#include <iostream>
#include "x.hpp"
int main() {
int a = 0;
std::cin >> a;
if (std::cin.good()) {
std::cout << a << "\n";
} else {
std::cout << x << "\n";
}
}
//x.cpp
int x = 10;
Совет: не используйте* глобальные переменные
9. Статические поля классов и статические переменные в функции
#include <iostream>#include <iostream>
struct Point {
int x, y;
};
int calculate(int a) {
static int shared = 0;
shared += a;
return shared;
}
struct Coord {
static Point origin = {0, 0};
Point delta;
};
int main() {
int i = 0;
std::cin >> i;
int main() {
std::cout << Coord::origin.x << " ";
std::cout << Coord::origin.y << "\n";
}
std::cout << calculate(i) << " ";
std::cout << calculate(i) << "\n";
}
Совет: не используйте* статические не константные переменные/поля
10. Dynamic storage duration
11. HEAP (она же «Куча») и Free Store
12. malloc/free vs. new/delete
1. malloc возвращает nullptr, если не удалосьвыделить память
2. malloc используется и для массивов и для
отдельных объектов
3. Память освобождается вызовом free
4. Данные располагаются в КУЧЕ
5. malloc/free – это функции из стандартной
библиотеки
6. ...
#include <iostream>
#include <memory>
int main() {
size_t n = 0;
std::cin >> n;
1. Оператор new (обычно) генерирует исключение, если не
удалось выделить память
2. Для массивов используется new[]. Для отдельных
объектов – new.
3. Освобождать память нужно с помощью соответствующего
new варианта delete: либо delete, либо delete[]
4. Данные располагаются во Free Store
5. new/new[] и delete/delete[] встроенные в язык
операторы
6. ...
#include <iostream>
int main() {
size_t n = 0;
std::cin >> n;
int * nums = static_cast< int * >(
malloc(sizeof(int) * n));
}
if (nums == nullptr) {
std::cerr << "Out of memory\n";
return 1;
}
//...
int * nums = nullptr;
try {
nums = new int[n];
} catch (const std::bad_alloc & e) {
std::cerr << "Out of memory\n";
return 1;
}
//...
free(nums);
delete[] nums;
}
13. malloc/free vs. new/delete
#include <iostream>#include <memory>
int main() {
size_t n = 0;
std::cin >> n;
#include <iostream>
int main() {
size_t n = 0;
std::cin >> n;
int * nums = static_cast< int * >(
malloc(sizeof(int) * n));
}
if (nums == nullptr) {
std::cerr << "Out of memory\n";
return 1;
}
int * nums = nullptr;
try {
nums = new int[n];
} catch (const std::bad_alloc & e) {
std::cerr << "Out of memory\n";
return 1;
}
for (size_t i = 0; i < n; ++i) {
std::cin >> nums[i];
}
if (!std::cin) {
free(nums);
return 1;
}
for (size_t i = 0; i < n; ++i) {
std::cin >> nums[i];
}
if (!std::cin) {
delete[] nums;
return 1;
}
free(nums);
delete[] nums;
}
14. Безопасность относительно исключений
15.
Система типов*НЕ ЯВЛЯЕТСЯ ТИПОМ*:
void
1. Тип (в узком смысле) – {char, ..., bool} + <указатели> + <указатели на указатели> + …
2. Класс (в техническом смысле) – средство композиции типов
3. Композиция типов – есть тип
4. Каждый тип задаёт множество конкретных объектов
5. Система типов – совокупность всех типов, их сочетаний и способов преобразования объектов
одного типа в объекты другого типа (в конкретной программе)
operator*
Система типов
Complex = {double, double};
Ratio = {int, unsigned};
int
()
main = () -> int
isReal = Complex -> bool
Ratio = {int,
isInteger = Ratio -> bool
bool
(int, int)
unsigned}
toComplex = Ratio -> Complex
abs = Complex -> double
double
//...
operator% = (int, int) -> int
Complex =
opeator! = bool -> bool
{double, double}
//...
Система типов – в любой программе конечна
16. Категории безопасности относительно исключений
Неформально: если возникнет исключение, программа будет в каком-то разумном состоянии и не произойдётутечек ресурсов
Более формально: преобразование в системе типов (выполнение функции, метода…) считается безопасным,
если при возникновении исключений, инварианты участвующих в преобразований объектов не нарушаются и
утечек ресурсов не происходит.
На практике полезно выделить следующие категории безопасности:
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любые операции
над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется исключение,
то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно (инварианты
соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было сгенерировано исключение, но
при этом состояние объектов, участвующих в преобразовании остаётся таким же, как и до выполнения
преобразования
Преобразования, не соответствующие одной из этих гарантий, считаются небезопасными (неформально:
поведение непредсказуемое, некорректное, трудоёмкое к сопровождению и так далее). Часто небезопасный
код принципиально не позволяет написать корректную программу.
17. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) {
assert(b != 0);
return a / b;
}
18. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ – исключения отсутствуют
19. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ – исключения отсутствуют
//UB, если b == 0
20.
21. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) noexcept {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ – исключения отсутствуют
//UB, если b == 0
//при анализе можем полагать, что
//пользователь об этом знает и избегает UB
22. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) noexcept {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ – исключения отсутствуют
//UB, если b == 0
//при анализе можем полагать, что
//пользователь об этом знает и избегает UB
int divide(int a, int b) {
if (b == 0) {
throw std::logic_error("div by 0");
}
return a / b;
}
23. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) noexcept {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ – исключения отсутствуют
//UB, если b == 0
//при анализе можем полагать, что
//пользователь об этом знает и избегает UB
int divide(int a, int b) {
if (b == 0) {
throw std::logic_error("div by 0");
}
return a / b;
} //БЕЗОПАСНАЯ – СТРОГО
24.
25. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
int divide(int a, int b) noexcept {
assert(b != 0);
return a / b;
} //БЕЗОПАСНАЯ* – исключения отсутствуют
//UB, если b == 0
//при анализе можем полагать, что
//пользователь об этом знает и избегает UB
int divide(int a, int b) {
if (b == 0) {
throw std::logic_error("div by 0");
}
return a / b;
} //БЕЗОПАСНАЯ – СТРОГО
26. Категории безопасности относительно исключений
1. Гарантия отсутствия исключений: преобразование не генерирует исключений (например, любыеоперации над встроенными типами не генерируют исключений [хотя некоторые приводят к UB]).
2. Базовая гарантия относительно исключений: если в процессе преобразования генерируется
исключение, то инварианты объектов не нарушаются, утечки ресурсов отсутствуют
3. Строгая гарантия относительно исключений: либо преобразование завершается успешно
(инварианты соблюдены, утечки ресурсов отсутствуют, исключений не было), либо было
сгенерировано исключение, но при этом состояние объектов, участвующих в преобразовании
остаётся таким же, как и до выполнения преобразования
long accumulate(
const std::vector< int > & v)
{
long sum = 0;
for (size_t i = 0; i < v.size(); ++i) {
sum += v.at(i);
}
return sum;
}
long accumulate(
const std::vector< int > & v)
{
long sum = 0;
for (size_t i = 0; i < v.size(); ++i) {
sum += v[i];
}
return sum;
}
27. Управление ресурсами
28.
Управление ресурсамиНеформально:
• «Ресурс» — всё, что имеет хоть сколько-нибудь ограниченный
характер
Часто используемые ресурсы:
• Файловые дескрипторы
• Мьютексы
• Шрифты и кисти в графических интерфейсах
• Соединение с БД
• Сетевые сокеты
• Дисковое пространство
• Открытые файлы
• …
• Динамическая память
• …
29.
Управление ресурсамиОсновная мысль: ресурсы стоит занимать,
забывать освобождать, когда они не нужны
когда
они
требуются
и
не
30.
Управление ресурсамиОсновная мысль: ресурсы стоит занимать,
забывать освобождать, когда они не нужны
когда
они
требуются
и
не
ресурсом
нельзя
Проблемы:
• Ресурс выделен,
воспользоваться
но
дескриптор
потерян
и
теперь
• Наличие нескольких дескрипторов ресурса и отсутствие возможности
определить с помощью дескриптора был ли освобождён ресурс, что
заставляет
разработчика
самостоятельно
отслеживать
освобождение
ресурсов
31.
Управление ресурсамиОсновная мысль: ресурсы стоит занимать,
забывать освобождать, когда они не нужны
когда
они
требуются
и
не
ресурсом
нельзя
Проблемы:
• Ресурс выделен,
воспользоваться:
но
дескриптор
потерян
и
теперь
• Утечка - принципиальное отсутствие возможности освободить ресурс
• Наличие нескольких дескрипторов ресурса и отсутствие возможности
определить с помощью дескриптора был ли освобождён ресурс, что
заставляет
разработчика
самостоятельно
отслеживать
освобождение
ресурсов.
• Двойное освобождение – повторная попытка освободить уже освобожденный ресурс
• Висячие дескрипторы – попытка использовать ресурс, который уже был освобождён
32.
Предотвращение утечкиресурсов (вручную)
33.
Предотвращение утечки ресурсовvoid foo() {
Object * exe = new Object();
//...
exe->execute(); //throws?
delete exe;
}
34.
Предотвращение утечки ресурсовvoid foo() {
Object * exe = new Object();
//...
exe->execute(); //throws?
delete exe;
}
void foo() {
Object * exe = new Object();
//...
try {
exe->execute(); //throws?
delete exe;
} catch (...) {
delete exe;
throws;
}
//delete exe;
}
35.
Предотвращение утечки ресурсовvoid bar() {
Object * exe = new Object();
Object * exeToo = new Object(); //throws?
//...
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
}
36.
Предотвращение утечки ресурсовvoid bar() {
Object * exe = new Object();
Object * exeToo = new Object(); //throws?
//...
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
}
void bar() {
Object * exe = nullptr;
Object * exeToo = nullptr;
try {
exeToo = new Object(); //throws?
exe = new Object();
//throws?
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
} catch (...) {
delete exe;
delete exeToo;
throw;
}
//delete exe;
//delete exeToo;
}
37.
Предотвращение утечки ресурсовvoid bar() {
Object * exe = new Object();
Object * exeToo = new Object(); //throws?
//...
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
}
Вывод: без КАЙФА!
void bar() {
Object * exe = nullptr;
Object * exeToo = nullptr;
try {
exeToo = new Object(); //throws?
exe = new Object();
//throws?
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
} catch (...) {
delete exe;
delete exeToo;
throw;
}
//delete exe;
//delete exeToo;
}
38.
Обобщение указателяКонцепция владения
39.
Владение• Владелец объекта – тот, кто непосредственно влияет на время жизни объекта (отвечает
за удаление объекта, когда он больше не нужен)
40.
Владение• Владелец объекта – тот, кто непосредственно влияет на время жизни объекта (отвечает
за удаление объекта, когда он больше не нужен)
• Например:
для автоматических объектов (локальная переменная в функции) владельцем является
структурный блок, в котором определён объект
для объекта, являющегося полем класса владельцем является экземпляр объемлющего класса
struct Object {
//владелец ^ a и b
A a;
B b;
};
void foo() {
Object * p0 = new Object();
// p0 по факту владелец ^
{ // <- владелец о1
Object o1;
//...
}
delete p0;
}
int * ptr1 = new int;
// ptr1 владелец ^
int * ptr2 = ptr1;
// ptr2 видимо тоже
//...
delete ptr1;
//или delete ptr2;
//не понятно кто
//главный...
41.
Владение• Владелец объекта – тот, кто непосредственно влияет на время жизни объекта (отвечает
за удаление объекта, когда он больше не нужен)
• Например:
для автоматических объектов (локальная переменная в функции) владельцем является
структурный блок, в котором определён объект
для объекта, являющегося полем класса владельцем является экземпляр объемлющего класса
• Достижимость != Владение:
Достижимость – имеется дескриптор объекта, но управление временем жизни отсутствует (т.е.
осуществляется непосредственно разработчиком)
struct Object {
//владелец ^ a и b
A a;
B b;
};
void foo() {
Object * p0 = new Object();
// p0 по факту владелец ^
{ // <- владелец о1
Object o1;
//...
}
delete p0;
}
int * ptr1 = new int;
// ptr1 владелец ^
int * ptr2 = ptr1;
// ptr2 видимо тоже
//...
delete ptr1;
//или delete ptr2;
//не понятно кто
//главный...
42.
Владение• Владелец объекта – тот, кто непосредственно влияет на время жизни объекта (отвечает
за удаление объекта, когда он больше не нужен)
• Например:
для автоматических объектов (локальная переменная в функции) владельцем является
структурный блок, в котором определён объект
для объекта, являющегося полем класса владельцем является экземпляр объемлющего класса
• Достижимость != Владение:
Достижимость – имеется дескриптор объекта, но управление временем жизни отсутствует (т.е.
осуществляется непосредственно разработчиком)
• Неразделяемое владение – только один дескриптор влияет на временя жизни объекта
Пример: ссылки в С++ не владеют объектами (как следствие легко получить ссылку на
недействительный объект)
Как реализуется: владелец и его объект обычно достижимы только по ссылке
• Разделяемое владение – несколько дескрипторов влияют на время жизни объекта
Пример: в С++ обычно пока на объект существует хотя бы один указатель, освобождать его не
следует (однако в языке владение не представлено)
Как реализуется: класс предоставляющий доступ (достижимость) к объекту и подсчитывающий
соответствующих ссылок. Объект удаляется, когда число ссылок становится равным 0
43.
Владение• Неразделяемое владение – только один дескриптор влияет на временя жизни объекта
Пример: ссылки в С++ не владеют объектами (как следствие легко получить ссылку на
недействительный объект)
Как реализуется: владелец и его объект обычно достижимы только по ссылке
• Разделяемое владение – несколько дескрипторов влияют на время жизни объекта
Пример: в С++ обычно пока на объект существует хотя бы один указатель, освобождать его не
следует (однако в языке владение не представлено)
• Как реализуется: класс предоставляющий доступ (достижимость) к объекту и подсчитывающий
соответствующих ссылок. Объект удаляется, когда число ссылок становится равным 0
Неразделяемое владение
Неразделяемое владение
Либо разделяемое, либо нет
struct Object {
//владелец ^ a и b
A a;
B b;
};
void foo() {
Object * p0 = new Object();
// p0 по факту владелец ^
{ // <- владелец о1
Object o1;
//...
}
delete p0;
}
int * ptr1 = new int;
// ptr1 владелец ^
int * ptr2 = ptr1;
// ptr2 видимо тоже
//...
delete ptr1;
//или delete ptr2;
//не понятно кто
//главный...
44.
Умные указатели [smart pointers]Реализация владения с
использованием RAII
45.
RAII• Назначение: автоматизировать управление
освобождающей ресурсы, на компилятор
ресурсами,
переложив
вызов
функции,
46.
RAII• Назначение: автоматизировать управление
освобождающей ресурсы, на компилятор
ресурсами,
переложив
вызов
функции,
• Идиома RAII (Resource Acquisition Is Initialization) - одна из основных идиом С++:
• Ресурс должен быть связан с объектом, для которого обычно происходит
автоматический вызов деструктора (например, для объектов в автоматической
области памяти)
• Пример: умные указатели как реализации идиомы RAII
47.
RAII• Назначение: автоматизировать управление
освобождающей ресурсы, на компилятор
ресурсами,
переложив
вызов
функции,
• Идиома RAII (Resource Acquisition Is Initialization) - одна из основных идиом С++:
• Ресурс должен быть связан с объектом, для которого обычно происходит
автоматический вызов деструктора (например, для объектов в автоматической
области памяти)
• Пример: умные указатели как реализации идиомы RAII
• Идиома != Паттерн проектирования:
• Идиома - устойчивые конструкции языка или приёмы, часто используемые в языке
• Паттерны проектирования - определённый набор отношений между языковыми
сущностями (например, классами), решающие известную задачу на уровне дизайна
программного обеспечения
48.
RAII• Назначение: автоматизировать управление
освобождающей ресурсы, на компилятор
ресурсами,
переложив
вызов
функции,
• Идиома RAII (Resource Acquisition Is Initialization) - одна из основных идиом С++:
• Ресурс должен быть связан с объектом, для которого обычно происходит
автоматический вызов деструктора (например, для объектов в автоматической
области памяти)
• Пример: умные указатели как реализации идиомы RAII
• Разделяемое владение
• std::shared_ptr + std::weak_ptr
• boost::intrusive_ptr
• Неразделяемое владение
• std::unique_ptr
• boost::scoped_ptr //вместо std::auto_ptr
• //Неудачная реализация: std::auto_ptr, deprecated –> не пользуемся
49.
Проблемы с ресурсами. Примерыvoid foo() {
Object * exe = new Object();
//...
exe->execute(); //throws?
delete exe;
}
ФУНКЦИИ НЕБЕЗОПАСНЫ
50.
Проблемы с ресурсами. Примерыvoid foo() {
Object * exe = new Object();
//...
exe->execute(); //throws?
delete exe;
}
void bar() {
Object * exe = new Object();
Object * exeToo = new Object(); //throws?
//...
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
}
ФУНКЦИИ НЕБЕЗОПАСНЫ
51.
Проблемы с ресурсами. Примерыvoid foo() {
Object * exe = new Object();
//...
exe->execute(); //throws?
delete exe;
}
void bar() {
Object * exe = new Object();
Object * exeToo = new Object(); //throws?
//...
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
}
int read_data(const char * fname,
Object & o) {
int fd = open(fname, O_RDONLY);
if (fd == -1) {
return 1;
}
o.read_from(fd); // exception???
close(fd);
}
ФУНКЦИИ НЕБЕЗОПАСНЫ
52.
RAIIНазначение: автоматизировать управление ресурсами,
функции, освобождающей ресурсы, на компилятор
переложив
вызов
int read_data(const char * fname, Object & o) {
int fd = open(fname, O_RDONLY);
if (fd == -1) {
return 1;
}
o.read_from(fd); // exception???
close(fd);
}
53.
RAIIНазначение: автоматизировать управление ресурсами,
функции, освобождающей ресурсы, на компилятор
//простейший RAII
class ScopedFd {
public:
ScopedFd(int fd):
fd_(fd)
{}
~ScopedFd() {
close(fd_);
}
private:
int fd_;
};
переложив
вызов
int read_data(const char * fname, Object & o) {
int fd = open(fname, O_RDONLY);
if (fd == -1) {
return 1;
}
o.read_from(fd); // exception???
close(fd);
}
54.
RAIIНазначение: автоматизировать управление ресурсами,
функции, освобождающей ресурсы, на компилятор
//простейший RAII
class ScopedFd {
public:
ScopedFd(int fd):
fd_(fd)
{}
~ScopedFd() {
close(fd_);
}
private:
int fd_;
};
переложив
вызов
int read_data(const char * fname, Object & o) {
int fd = open(fname, O_RDONLY);
if (fd == -1) {
return 1;
}
o.read_from(fd); // exception???
close(fd);
}
int read_data(const char * fname, Object & o) {
int fd = fopen(fname, O_RDONLY);
if (fd == -1) {
return 1;
}
ScopedFd scoped(fd);
// any exception operations
o.read_from(fd);
}
55.
Решение проблемы утечки ресурсовvoid foo() {
Object * exe = nullptr;
Object * exeToo = nullptr;
try {
exeToo = new Object(); //throws?
exe = new Object();
//throws?
exe->execute();
//throws?
exeToo->execute();
//throws?
delete exe;
delete exeToo;
} catch (...) {
delete exe;
delete exeToo;
throw;
}
//delete exe;
//delete exeToo;
}
void foo() {
auto exe = std::unique_ptr< Object >(new Object);
auto exeToo = std::unique_ptr< Object >(new Object);
exe->execute();
exeToo->execute();
}
56.
Проблемы, возникающие прииспользовании умных указателей
57.
Умные указатели. RAII//...
char foo() {
//any exception?..
}
int sap(int * n)
noexcept {
//...
//...
}
void bar(char c, int n);
//...
//...
try {
bar(foo(), sap(new int(0)));
} catch (...) {
//...
}
//...
58.
Умные указатели. RAII//...
char foo() {
//any exception?..
}
int sap(int * n)
noexcept {
//...
//...
}
void bar(char c, int n);
//...
//...
try {
bar(foo(), sap(new int(0)));
} catch (...) {
//...
}
//...
Порядок вычислений - unspecified (до С++17):
• Порядок, приводящий к утечке:
• new int
• foo() –> с генерацией исключения
• Вывод: небезопасно!
59.
Умные указатели. RAII//...
char foo() {
//any exception?..
}
int sap(std::shared_ptr< int > n)
noexcept {
//...
//...
}
void bar(char c, int n);
//...
//...
try {
bar(foo(), sap(std::shared_ptr< int >(new int(0)));
} catch (...) {
//...
}
//...
Порядок вычислений - unspecified (до С++17):
• Порядок, приводящий к утечке:
• new int
• foo() –> с генерацией исключения
• Вывод: небезопасно!
60.
Умные указатели. RAII//...
char foo() {
//any exception?..
}
int sap(std::shared_ptr< int > n)
noexcept {
//...
//...
}
void bar(char c, int n);
//...
//...
try {
bar(foo(), sap(std::make_shared(0));
} catch (...) {
//...
}
//...
Порядок вычислений - unspecified (до С++17):
• Не существует порядка вычислений,
приводящего к утечке
• Вывод: безопасно
61.
make_sharedvs.
shared_ptr(...)
62.
make_sharedНюанс
//...
try {
auto shp = std::shared_ptr< int >(new int(0));
bar(foo(), sap(shp));
} catch (...) {
//...
}
Раздельное размещение
//...
объекта и счётчика ссылок
//...
try {
bar(foo(), sap(std::make_shared< int >(0));
} catch (...) {
//...
}
Совместное размещение
//...
объекта и счётчика ссылок
object
object
...
refs
refs
63.
Проблема циклических ссылок64.
Проблема циклических ссылок в RCSPstruct Person {
Person * friend;
};
int main() {
//brrrr
{
auto pKate = new Person;
auto pIvan = new Person;
pKate->friend = pIvan;
pIvan->friend = pKate;
delete pIvan;
delete pKate;
}
}
[RCSP – Reference-Counting Smart Pointer]
65.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
Person * friend;
};
int main() {
//brrrr
{
auto pKate = new Person;
auto pIvan = new Person;
pKate->friend = pIvan;
pIvan->friend = pKate;
delete pIvan;
delete pKate;
}
Ivan
}
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
66.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//brrrr
{
auto pKate = shp(new Person);
auto pIvan = shp(new Person);
pKate->friend = pIvan;
pIvan->friend = pKate;
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
}
Person::~Person() {
//~ for friend
}
67.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//brrrr
{
auto pKate = shp(new Person);
auto pIvan = shp(new Person);
pKate->friend = pIvan;
pIvan->friend = pKate;
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Ivan
}
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
68.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//brrrr
{
auto pKate = shp(new Person);
auto pIvan = shp(new Person);
pKate->friend = pIvan;
pIvan->friend = pKate;
// ~ for pIvan
// ~ for pKate
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Person::~Person() {
//~ for friend
}
69.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
// ~ for pKate
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Person::~Person() {
//~ for friend
}
70.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Person::~Person() {
//~ for friend
}
71.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
//1
1
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Person::~Person() {
//~ for friend
}
72.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
//1
1
}
//no ~ for Person?
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Person::~Person() {
//~ for friend
}
73.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
//1
1
}
//no ~ for Person?
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
74.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
//1
1
}
//no ~ for Person?
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
75.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::shared_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//2
1
pIvan->friend = pKate;
//2
2
// ~ for pIvan
//1
2
// ~ for pKate
//1
1
}
//no ~ for Person?
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
Ivan’s
friend
...
ВНИМАНИЕ!
УТЕЧКА
...
...
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
76.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::weak_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//1
1
pIvan->friend = pKate;
//1
1
// ~ for pIvan and Person
//1
0
// ~ for pKate and Person
//0
0
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
}
Person::~Person() {
//~ for friend
}
77.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::weak_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//1
1
pIvan->friend = pKate;
//1
1
// ~ for pIvan and Person
//1
0
// ~ for pKate and Person
//0
0
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
}
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
78.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::weak_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//1
1
pIvan->friend = pKate;
//1
1
// ~ for pIvan and Person
//1
0
// ~ for pKate and Person
//0
0
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
}
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
79.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::weak_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//1
1
pIvan->friend = pKate;
//1
1
// ~ for pIvan and Person
//1
0
// ~ for pKate and Person
//0
0
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
Ivan
}
Ivan’s
friend
pIvan
...
pKate
Kate
Kate’s
friend
Person::~Person() {
//~ for friend
}
80.
Проблема циклических ссылок в RCSP[RCSP – Reference-Counting Smart Pointer]
struct Person {
std::weak_ptr< Person > friend;
};
using shp = std::shared_ptr< Person >;
int main() {
//Счётчик ссылок
//brrrr
//Ivan - Kate
{
//_
_
auto pKate = shp(new Person); //_
1
auto pIvan = shp(new Person); //1
1
pKate->friend = pIvan;
//1
1
pIvan->friend = pKate;
//1
1
// ~ for pIvan and Person
//1
0
// ~ for pKate and Person
//0
0
}
shared_ptr< ... >::~shared_ptr() {
if (ref > 1) {
--ref;
} else {
destroy_object(obj);
}
}
...
}
...
...
...
...
...
...
Person::~Person() {
//~ for friend
}
81.
Задача82.
ЗадачаЗадача: замените в программе сырые указатели на умные, исправьте сигнатуры функций.
Обоснуйте свой ответ. Книги и массивы из книг располагаются во Free Store.
struct Book {
size_t id;
// список первоисточников
size_t ref_count_;
Book ** refs_;
};
// считать массив из указателей на книги
std::pair< Book **, size_t > read_lib(std::istream & in);
// получить указатель на выбранную пользователем книгу
Book * choose_book(std::pair< Book **, size_t > lib,
std::istream & in);
// найти книги, которые ссылаются на указанную
std::pair< Book **, size_t > find_refs(Book * b,
std::pair< Book **, size_t > lib);
// вывести список книг
void out(std::pair< Book **, size_t > lib);
int main() {
auto lib = read_lib(std::cin);
auto b = choose_book(lib, std::cin);
auto ref_to = find_refs(b, lib);
out(std::cout, ref_to);
std::cout << "\n";
}
Возможные варианты:
• std::unique_ptr< T[] >
• std::unique_ptr< T >
• std::shared_ptr< T >
• Массив
• Отдельное значение
• std::weak_ptr< T >
Пример shared_ptr для массива:
shared_ptr< int > p(new int[10],
std::default_delete< int[] >{});
83.
Задача (попроще)84.
Пример: задачаЗадача: реализуйте RAII массива массивов и исправьте функцию
std::pair< Object *, Object * > bar() {
Object * exe = new Object();
Object * exeToo = new Object();
//...
exe->execute();
exeToo->execute();
//...
return std::make_pair(exe, exeToo);
}
85.
Пример: задачаЗадача: реализуйте RAII массива массивов и исправьте функцию
// RAII для Object
struct RAIIObj {
RAIIObj(Object * p)
~RAIIObj()
Object * get();
void release();
private:
Object * p_;
};
std::pair< Object *, Object * > bar() {
Object * exe = new Object();
Object * exeToo = new Object();
//...
exe->execute();
exeToo->execute();
//...
return std::make_pair(exe, exeToo);
}
86.
Пример: задачаЗадача: реализуйте RAII массива массивов и исправьте функцию
// RAII для Object
struct RAIIObj {
RAIIObj(Object * p):
p_(p)
{}
~RAIIObj() {
delete p_;
}
Object * get() {
return p_;
}
void release() {
p_ = nullptr;
}
private:
Object * p_;
};
std::pair< Object *, Object * > bar() {
Object * exe = new Object();
Object * exeToo = new Object();
//...
exe->execute();
exeToo->execute();
//...
return std::make_pair(exe, exeToo);
}
87.
Пример: задачаЗадача: реализуйте RAII массива массивов и исправьте функцию
// RAII для Object
struct RAIIObj {
RAIIObj(Object * p):
p_(p)
{}
~RAIIObj() {
delete p_;
}
Object * get() {
return p_;
}
void release() {
p_ = nullptr;
}
private:
Object * p_;
};
std::pair< Object *, Object * > bar() {
Object * exe = new Object();
Object * exeToo = new Object();
//...
exe->execute();
exeToo->execute();
//...
return std::make_pair(exe, exeToo);
}
std::pair< Object *, Object * > bar() {
RAIIObj raiiexe(new Object());
RAIIObj raiiexeToo(new Object());
//...
raiiexe.get()->execute();
raiiexeToo.get()->execute();
//...
auto p1 = raiiexe.get();
auto p2 = raiiexeToo.get();
raiiexe.release();
raiiexeToo.release();
return std::make_pair(p1, p2);
}
88.
ЗадачаЗадача: реализуйте RAII массива массивов и исправьте функцию
// RAII для Object
struct RAIIObj {
RAIIObj(Object * p):
p_(p)
{}
~RAIIObj() {
delete p_;
}
Object * get() {
return p_;
}
void release() {
p_ = nullptr;
}
private:
Object * p_;
};
// создание массива из m массивов размера n
int ** create_mtx(size_t m, size_t n) {
int ** mtx = new int*[m];
for (size_t i = 0; i < m; ++i) {
mtx[i] = new int[n];
//исключение приведёт к утечке
//всех данных
}
return mtx;
}
Программирование