Почему в стандартной библиотеке нет средств борьбы с висячими ссылками и как это исправить?
Как же предлагают решить эту проблему разработчики языка?
На сайте 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, указателя котроый зануляется при удалении объекта.
- Детектирует время и место доступа по невалидному указателю. Хотя, как правило, более интересен момент, когда происходит удаление объекта, на который есть указатели. По этой же причине инструментация детектирует не все случаи неправильного доступа, а лишь те, которые реально отработали при тестировании.
Надеюсь библиотека окажется полезной С++ разработчикам. Возможно есть другие способы решения подобных проблем, с удовольствием выслушаю в комментариях.
P.S. Еще раз, для удобства, привожу ссылку на код библиотеки RSL.
Комментарии (1)
3 апреля 2017 в 14:27
+1↑
↓
Элджера читали? 20 лет изобретают сборку мусора для C++.