Після появи у стандартній бібліотеці C++ розумних покажчиків, проблема керування часом життя об'єкта була вирішена. Можна створювати об'єкти на стеку, тоді вони автоматичесті вилучаться при виході з області видимості, або використовувати unique_ptr для створення об'єктів з екслюзивним володінням або shared_ptr для спільного володіння. Але тільки для shared_ptr у стандартній бібліотеці існує неволодіючий індекс weak_ptr, який запобігає використанню невалидного індексу. Для інших випадків використовують «старі і небезпечні» raw pointers.
Як же пропонують вирішити цю проблему розробники мови?
На сайті CppCoreGuidelines є кілька цікавих документів (тут і тут).
Основний посил такий: мені не можемо реалізувати безпечні неволодіючі покажчики, не порушуючи zero overhead principle. Тому ми не будемо реалізовувати такі кошти в runtime, а постараємося вирішити проблему статичним аналізом коду.
Я не згоден з такою позицією і ось мої доводи:
- Статичний аналіз не зможе відловити всі проблеми. Самі автори статей говорять про те, що іноді прийдеться позначати деякі конструкції як безпечні, залишаючи питання коректності коду на совісті розробника.
- Для того, що б статичний аналіз запрацював, потрібна підтримка з боку компіляторів. Коли вона з'явиться - невідомо.
- Статичний аналіз не дозволяє мати слабкі неволодіючі індекси (аналог weak_ptr, який зануляється при видаленні об'єкта).
- Реалізація таких покажчиків порушує zero overhead principle - це правда. Але, на мій погляд, це принцип також порушують shared_ptr, оскільки має додатковий ref_count об'єкт або, наприклад string з його small string optimization. У будь-якому випадку, стандартна бібліотека надає класи з чітко описаними характеристиками, а програміст вирішує підходять ці класи для нього чи ні.
На мою думку, стандартна бібліотека повинна надавати класи для всіх основних сценаріїв програмування. Неволодіючі індекси і доступ за висячими посиланнями велика і досі незакрита тема в С++. Думаю, стандартна бібліотека повинна надавати програмісту опціональну можливість мати такі покажчики.
У своєму проекті RSL я спробував реалізувати такий покажчик. Основна ідея не нова: необхідний об'єкт, який при руйнуванні буде нотифікувати покажчики про факт видалення.
Таким чином ми маємо два класи:
- rsl::track::trackable - клас, який сповіщає індекси при вилученні.
- rsl::track::pointer - власне неволодіючий індекс.
Коли rsl::track::pointer'и вказують на один і той самий rsl::track::trackable об'єкт, вони вибудовуються в двозв'язковий список. Курсор на голову списку міститься в rsl::track::trackable. Таким чином, створення покажчиків займає костянтний час. Розмір rsl::track::trackable складає один індекс, а rsl::track::pointer - 4 індекси (покажчик на об'єкт, два індекси для організації списку і ще один для реалізації поліморфної поведінки). Можливо більш оптимальна організація покажчиків, якщо хто знає, прошу розповісти.
Так само дана реалізація не потокобезпека, для забезпечення роботи в різних потоках прийдеться додавати std::atomic_flag і уповільнювати модифікацію покажчиків.
Крім того, з появою allocator aware containers, з'явилася можливість реалізувати аллокатор, який дозволяє використовувати rsl::track::pointer зі стандартними контейнерами. Основна ідея в тому, що тепер всі алокації в контейнерах робляться екземпляром аллокатора, що зберігається в контейнері, або його шаблонної копії, і ми можемо зберігати rsl::track::trackable в аллокаторі і передавати його в копії.
У тестах наведено приклади роботи з основними стандартними контейнерами, включаючи std::array, а також unique_ptr.
На закінчення хочу навести ще один сценарій, в якому rsl::track::pointer буде корисно. Це ситуації, аналогічні delete this. Зазвичай таке відбувається побічно при виклику зовнішнього по відношенню до об'єкта коду, функтора або сигналу. Такого роду помилки рідкісні, але важко вловимі.
Для таких випадків (та й будь-яких інших проблем з доступом за висячим посиланням) використовують такі засоби як google sanitizers, які дозволяють відловлювати подібні проблеми.
Але ці кошти мають свої недоліки:
- Працюють не скрізь (не на всіх платформах).
- Необхідна інструменталізація коду - код відрізняється від продакшену.
- Немає аналога weak_ptr, індексу котрий зануляється при видаленні об'єкта.
- Детектує час і місце доступу за невалидним покажчиком. Хоча, як правило, більш цікавий момент, коли відбувається видалення об'єкта, на який є покажчики. З цієї ж причини інструментація детектує не всі випадки неправильного доступу, а лише ті, які реально відпрацювали при тестуванні.
Сподіваюся бібліотека виявиться корисною C++ розробникам. Можливо є інші способи вирішення подібних проблем, із задоволенням вислухаю в коментарях.
P.S. Ще раз, для зручності, наводжу посилання на код бібліотеки RSL.