32
Детально про найпростіші “розумні” вказівники C++ || The C++'s simplest smart pointers in depth

IT Talks The c++'s simplest smart pointers in depth

  • Upload
    -

  • View
    109

  • Download
    0

Embed Size (px)

Citation preview

Page 1: IT Talks The c++'s simplest smart pointers in depth

Детально про найпростіші “розумні” вказівники C++

||The C++'s simplest smart pointers

in depth

Page 2: IT Talks The c++'s simplest smart pointers in depth

Керування пам'яттю в C++ Ручне керування пам'яттю

Отримання ресурсу є ініціалізація == Resource acquisition is initialization == RAII

Page 3: IT Talks The c++'s simplest smart pointers in depth

RAII

Кожен ресурс обгортається в клас, в якому: конструктор отримує ресурс і встановлює всі

інваріанти класу, або кидає виключення, якщо це не вдається зробити

деструктор звільняє ресурс і не кидає виключень Ресурс завжди зберігається в об'єкті RAII-

класу, який створений на стеку чи тимчасовий, або має час існування обмежений іншим таким об'єктом

Безпека виняткових ситуацій == ES Полегшує ранній вихід із функції чи циклу

Page 4: IT Talks The c++'s simplest smart pointers in depth

Переваги RAII над збирачем сміття (GC)

Уніформне керування ресурсами: оперативна пам'ять, потік (thread), відкритий сокет, відкритий файл, заблокований мʼютекс, з'єднання із БД

Передбачуваний час існування об'єкта Сміття

Ефективне використання пам'яті Відсутність неконтрольованих затримок для

видалення сміття

Page 5: IT Talks The c++'s simplest smart pointers in depth

Переваги збирача сміття над RAII

GC простіше використовувати в простих ситуаціях

GC дозволяє ефективно реалізувати деякі постійні (persistent) структури даних

RAII вимагає дисципліни розробника Код багатьох програм написаний на древньому

C++, частково на C Видалення двічі (UB) Розіменування “висячого” вказівника (UB) Протікання пам'яті Навіть коректний код складно

модифікувати/рефакторити

Page 6: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 11 bool showDonut(int windowId)2 {3 Painter * const painter = makePainter(windowId);4 return painter->showDonut();5 }

Page 7: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 8: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 9: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 10: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 11: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 12: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 13: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 14: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 15: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 16: IT Talks The c++'s simplest smart pointers in depth

Приклад “Тимчасовий вказівник” 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 }

Page 17: IT Talks The c++'s simplest smart pointers in depth

Приклад “Інтерфейс” 11 // Interface 12 Cookie * makeCookie(int size);3 bool showCookie(Cookie * cookie);45 // Interface 26 Cookie * getCookie(int size);7 bool eatCookie(Cookie * cookie);

Page 18: IT Talks The c++'s simplest smart pointers in depth

Приклад “Інтерфейс” 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);

Page 19: IT Talks The c++'s simplest smart pointers in depth

Приклад “Інтерфейс” 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);

Page 20: IT Talks The c++'s simplest smart pointers in depth

Приклад “Інтерфейс” 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);

Page 21: IT Talks The c++'s simplest smart pointers in depth

Приклад “Користувач” 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";

Page 22: IT Talks The c++'s simplest smart pointers in depth

Приклад “Користувач” 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;

Page 23: IT Talks The c++'s simplest smart pointers in depth

Ефективність 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 — порожній клас)

Page 24: IT Talks The c++'s simplest smart pointers in depth

unique_ptr чи стек?

Варто оголошувати об'єкти на стеку у функціях та за значенням у класах коли це можливо: вбудовані типи, класи стандартної бібліотеки, інші прості структури та класи. Це простіше і ефективніше.

Поліморфний об'єкт Вказівник на реалізацію (класу) ==

Pointer to implementation == Pimpl idiom == Opaque pointer

Page 25: IT Talks The c++'s simplest smart pointers in depth

unique_ptr чи стек? (2)

Потрібен стан відсутності - nullptr (краще std::optional із C++17)

Адреса об'єкта повинна бути сталою, але власник об'єкта може змінюватись

Переміщення об'єкту повільне або неможливе

Потрібне особливе видалення (custom deleter)

Page 26: IT Talks The c++'s simplest smart pointers in depth

Приклад “Особливе видалення”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);

Page 27: IT Talks The c++'s simplest smart pointers in depth

Приклад 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());

Page 28: IT Talks The c++'s simplest smart pointers in depth

Приклад 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, ...)

Page 29: IT Talks The c++'s simplest smart pointers in depth

std::auto_ptr

Доступний в C++98 Не інтуїтивна семантика копіювання Не може бути використаний в контейнерах Deprecated in C++11 Removed in C++17 Може бути легко замінений на unique_ptr

Page 30: IT Talks The c++'s simplest smart pointers in depth

Приклад “Вказівник на реалізацію”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;

Page 31: IT Talks The c++'s simplest smart pointers in depth

Спеціалізація для масивів

Зазвичай 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>;

Page 32: IT Talks The c++'s simplest smart pointers in depth

Ваші запитання