Upload
-
View
109
Download
0
Embed Size (px)
Citation preview
Детально про найпростіші “розумні” вказівники C++
||The C++'s simplest smart pointers
in depth
Керування пам'яттю в C++ Ручне керування пам'яттю
Отримання ресурсу є ініціалізація == Resource acquisition is initialization == RAII
RAII
Кожен ресурс обгортається в клас, в якому: конструктор отримує ресурс і встановлює всі
інваріанти класу, або кидає виключення, якщо це не вдається зробити
деструктор звільняє ресурс і не кидає виключень Ресурс завжди зберігається в об'єкті RAII-
класу, який створений на стеку чи тимчасовий, або має час існування обмежений іншим таким об'єктом
Безпека виняткових ситуацій == ES Полегшує ранній вихід із функції чи циклу
Переваги RAII над збирачем сміття (GC)
Уніформне керування ресурсами: оперативна пам'ять, потік (thread), відкритий сокет, відкритий файл, заблокований мʼютекс, з'єднання із БД
Передбачуваний час існування об'єкта Сміття
Ефективне використання пам'яті Відсутність неконтрольованих затримок для
видалення сміття
Переваги збирача сміття над RAII
GC простіше використовувати в простих ситуаціях
GC дозволяє ефективно реалізувати деякі постійні (persistent) структури даних
RAII вимагає дисципліни розробника Код багатьох програм написаний на древньому
C++, частково на C Видалення двічі (UB) Розіменування “висячого” вказівника (UB) Протікання пам'яті Навіть коректний код складно
модифікувати/рефакторити
Приклад “Тимчасовий вказівник” 11 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 return painter->showDonut();5 }
Приклад “Тимчасовий вказівник” 21 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 const bool result = painter->showDonut();5 delete painter;6 return result;7 }
Приклад “Тимчасовий вказівник” 31 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 if (!painter->hasKitchen())5 return false;6 const bool result = painter->showDonut();7 delete painter;8 return result;9 }
Приклад “Тимчасовий вказівник” 41 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 if (!painter->hasKitchen()) {5 delete painter;6 return false;7 }8 const bool result = painter->showDonut();9 delete painter;10 return result;11 }
Приклад “Тимчасовий вказівник” 51 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 try {5 if (!painter->hasKitchen()) {6 delete painter;7 return false;8 }9 const bool result = painter->showDonut();10 delete painter;11 return result;12 }13 catch (...) {14 delete painter;15 throw;16 }17 }
Приклад “Тимчасовий вказівник” 61 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 try {5 Kitchen * const kitchen = painter->makeKitchen();6 if (!kitchen) {7 delete painter;8 return false;9 }10 const bool result = kitchen->showDonut();11 delete kitchen;12 delete painter;13 return result;14 }15 catch (...) {16 delete painter;17 throw;18 }19 }
Приклад “Тимчасовий вказівник” 71 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 Kitchen * kitchen = nullptr;5 try {6 kitchen = painter->makeKitchen();7 if (!kitchen) {8 delete painter;9 return false;10 }11 const bool result = kitchen->showDonut();12 delete kitchen;13 delete painter;14 return result;15 }16 catch (...) {17 delete kitchen;18 delete painter;19 throw;20 }21 }
Приклад “Тимчасовий вказівник” 21 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 const bool result = painter->showDonut();5 delete painter;6 return result;7 }
1 bool showDonut(int windowId)2 {3 using Ptr = const std::unique_ptr<Painter>;4 Ptr painter(makePainter(windowId));5 return painter->showDonut();6 }
Приклад “Тимчасовий вказівник” 41 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 if (!painter->hasKitchen()) {5 delete painter;6 return false;7 }8 const bool result = painter->showDonut();9 delete painter;10 return result;11 }
1 bool showDonut(int windowId)2 {3 using Ptr = const std::unique_ptr<Painter>;4 Ptr painter(makePainter(windowId));5 if (!painter->hasKitchen())6 return false;7 return painter->showDonut();8 }
Приклад “Тимчасовий вказівник” 51 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 try {5 if (!painter->hasKitchen()) {6 delete painter;7 return false;8 }9 const bool result = painter->showDonut();10 delete painter;11 return result;12 }13 catch (...) {14 delete painter;15 throw;16 }17 }
1 bool showDonut(int windowId)2 {3 using Ptr = const std::unique_ptr<Painter>;4 Ptr painter(makePainter(windowId));5 if (!painter->hasKitchen())6 return false;7 return painter->showDonut();8 }
Приклад “Тимчасовий вказівник” 71 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 Kitchen * kitchen = nullptr;5 try {6 kitchen = painter->makeKitchen();7 if (!kitchen) {8 delete painter;9 return false;10 }11 const bool result = kitchen->showDonut();12 delete kitchen;13 delete painter;14 return result;15 }16 catch (...) {17 delete kitchen;18 delete painter;19 throw;20 }21 }
1 bool showDonut(int windowId)2 {3 using Ptr = const std::unique_ptr<Painter>;4 Ptr painter(makePainter(windowId));5 using Kptr = const std::unique_ptr<Kitchen>;6 Kptr kitchen(painter->makeKitchen());7 if (!kitchen)8 return false;9 return kitchen->showDonut();10 }
Приклад “Інтерфейс” 11 // Interface 12 Cookie * makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(Cookie * cookie);
Приклад “Інтерфейс” 2
1 // Interface 12 Cookie * makeCookie(int size);3 bool showCookie(Cookie & cookie);45 // Interface 26 Cookie & getCookie(int size);7 bool eatCookie(Cookie * cookie);
1 // Interface 12 Cookie * makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(Cookie * cookie);
Приклад “Інтерфейс” 31 // Interface 12 Cookie * makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(Cookie * cookie);
1 // Interface 12 std::unique_ptr<Cookie> makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(std::unique_ptr<Cookie> && cookie);
Приклад “Інтерфейс” 4
1 // Interface 12 std::unique_ptr<Cookie> makeCookie(int size);3 bool showCookie(std::observer_ptr<Cookie> cookie);4 // std::observer_ptr may land in C++2x5 // Interface 26 std::observer_ptr<Cookie> getCookie(int size);7 bool eatCookie(std::unique_ptr<Cookie> && cookie);
1 // Interface 12 std::unique_ptr<Cookie> makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(std::unique_ptr<Cookie> && cookie);
Приклад “Користувач” 32 std::unique_ptr<Cookie> makeCookie(int size);7 bool eatCookie(std::unique_ptr<Cookie> && cookie);
1 auto cookie = makeCookie(47);2 cookie->coverWithCream();3 if (eatCookie(std::move(cookie)))4 std::cout << "Yum!\n";
Приклад “Користувач” 12 Cookie * makeCookie(int size);7 bool eatCookie(Cookie * cookie);
1 auto * cookie = makeCookie(47);2 try {3 cookie->coverWithCream();4 }5 catch (...) {6 delete cookie;7 throw;8 }9 if (eatCookie(cookie)) {10 std::cout << "Yum!\n";11 cookie = nullptr;12 }13 else14 delete cookie;
Ефективність unique_ptr
Реалізація unique_ptr використовує Empty base optimization (EBO)
Кожна операція над unique_ptr теоретично повинна бути такою ж швидкою, як відповідна операція над “голим” вказівником
template <class T, class Deleter = std::default_delete<T>> class unique_ptr;
sizeof(std::unique_ptr<T, Deleter>) == sizeof(T *) // (якщо Deleter — порожній клас)
unique_ptr чи стек?
Варто оголошувати об'єкти на стеку у функціях та за значенням у класах коли це можливо: вбудовані типи, класи стандартної бібліотеки, інші прості структури та класи. Це простіше і ефективніше.
Поліморфний об'єкт Вказівник на реалізацію (класу) ==
Pointer to implementation == Pimpl idiom == Opaque pointer
unique_ptr чи стек? (2)
Потрібен стан відсутності - nullptr (краще std::optional із C++17)
Адреса об'єкта повинна бути сталою, але власник об'єкта може змінюватись
Переміщення об'єкту повільне або неможливе
Потрібне особливе видалення (custom deleter)
Приклад “Особливе видалення”1 // Interface2 struct IppDeleter3 {4 void operator()(Ipp64f * p) const { ippsFree(p); }5 };67 using Ipp64fUniquePtr = std::unique_ptr<Ipp64f, IppDeleter>;89 inline Ipp64fUniquePtr makeUniqueIpp64f(int len)10 {11 return Ipp64fUniquePtr(ippsMalloc_64f(len));12 }1314 // Usage15 auto factor = makeUniqueIpp64f(3);
Приклад make_unique (C++14)1 // Interface2 void combine(std::unique_ptr<A> && a, std::unique_pr<B> && b);
1 // Usage 1 (exception unsafe because of a possible interleaved order of2 // execution of subexpressions in function parameters)3 combine(std::unique_ptr<A>(new A(2)), std::unique_ptr<B>(new B));4 combine(std::unique_ptr<A>(new A(2)), createB());
1 // Usage 2 (exception safe)2 combine(std::make_unique<A>(2), std::make_unique<B>());3 combine(std::make_unique<A>(2), createB());
Приклад make_unique 2 (C++14)1 // Declaration2 std::vector<std::unique_ptr<Biscuit>> biscuits;
1 // Usage 1 (line 2 is long; line 3 is exception unsafe: if emplace_back throws)2 biscuits.push_back(std::unique_ptr<Biscuit>(new Biscuit));3 biscuits.emplace_back(new Biscuit);
1 // Usage 2 (exception safe)2 biscuits.push_back(std::make_unique<Biscuit>());3 biscuits.emplace_back(std::make_unique<Biscuit>());
1 // "Naked" declaration2 std::vector<Biscuit *> biscuits;
1 // "Naked" usage 1 (exception unsafe)2 biscuits.push_back(new Biscuit);
1 // "Naked" usage 2 (exception safe)2 biscuits.push_back(nullptr);3 biscuits.back() = new Biscuit;
Як щодо асоціативних контейнерів? (std::set, std::map, std::unordered_set, std::multimap, ...)
std::auto_ptr
Доступний в C++98 Не інтуїтивна семантика копіювання Не може бути використаний в контейнерах Deprecated in C++11 Removed in C++17 Може бути легко замінений на unique_ptr
Приклад “Вказівник на реалізацію”1 // Spacer.h2 class Spacer3 {4 public:5 Spacer();6 ~Spacer();7 // ...8 private:9 class Impl;10 std::unique_ptr<Impl> impl_;11 };1213 // Spacer.cpp14 class Spacer::Impl15 {16 // ...17 };18 Spacer::Spacer() : impl_(std::make_unique<Impl>()) {}19 // Destructor definition must be in the cpp file because the20 // implicitly invoked Impl's destructor needs complete type21 Spacer::~Spacer() = default;
Спеціалізація для масивів
Зазвичай std::vector або std::string зручніші. Функція, яку не можна змінити (бібліотечна
функція), приймає/вертає вказівник на масив unique_ptr<T[]> ефективніший в деяких
ситуаціях: new T[n] виділяє пам'ять для рівно n елементів new T[n] не ініціює елементи, якщо T - POD
template <class T, class Deleter> class unique_ptr<T[], Deleter>;
Ваші запитання