Технологии программирования Занятие #2
«Классы памяти»
Понятия стандарта [ISO/IEC 14882 // [lex.separate] [basic.def.odr]]
Automatic storage duration
Static storage duration
Строковые литералы (не путать с массивом символов)
Глобальные переменные
Статические поля классов и статические переменные в функции
Dynamic storage duration
HEAP (она же «Куча») и Free Store
malloc/free vs. new/delete
malloc/free vs. new/delete
Безопасность относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Категории безопасности относительно исключений
Управление ресурсами
839.27K
Категория: ПрограммированиеПрограммирование

Технологии программирования. Занятие #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-ONLY
memory

'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.hpp
extern 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_shared
vs.
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.

Проблема циклических ссылок в RCSP
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;
}
}
[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;
}
English     Русский Правила