Почему в стандартной библиотеке нет средств борьбы с висячими ссылками и как это исправить?

в 10:28, , рубрики: c++, open source, Программирование, С++

После появления в стандартной библиотеке С++ умных указателей, проблема управления временем жизни объекта была решена. Можно создавать объекты на стеке, тогда они автоматичести удалятся при выходе из области видимости, либо использовать unique_ptr для создания объектов с экслюзивным владением или shared_ptr для совместного владения. Но только для shared_ptr в стандартной библиотеке существует невладеющий указатель weak_ptr, который предотвращает использование невалидного указателя. Для остальных случаев используют «старые и опасные» raw pointers.

Как же предлагают решить эту проблему разработчики языка?

На сайте CppCoreGuidelines есть несколько любопытных документов (здесь и здесь).
Основной посыл таков: мне не можем реализовать безопасные невладеющие указатели, не нарушая zero overhead principle. Поэтому мы не будем реализовывать такие средства в runtime, а постараемся решить проблему статическим анализом кода.

Я не согласен с такой позицией и вот мои доводы:

  1. Статический анализ не сможет отловить все проблемы. Сами авторы статей говорят о том, что иногда прийдется помечать некоторые конструкции как безопасные, оставляя вопрос корректности кода на совести разработчика.
  2. Для того, что бы статический анализ заработал, нужна поддержка со стороны компиляторов. Когда она появится — неизвестно.
  3. Статический анализ не позволяет иметь слабые невладеющие указатели (аналог weak_ptr, который зануляется при удалении объекта).
  4. Реализация таких указателей нарушает zero overhead principle — это правда. Но, на мой взгляд, это принцип также нарушают shared_ptr, т.к. имеет дополнительный ref_count объект или, например string с его small string optimization. В любом случае, стандартная библиотека предоставляет классы с четко описанными характеристиками, а программист решает подходят эти классы для него или нет.

По моему мнению, стандартная библиотека должна предоставлять классы для всех основных сценариев программирования. Невладеющие указатели и доступ по висячим ссылкам большая и до сих пор незакрытая тема в С++. Думаю стандартная библиотека должна предоставлять программисту опциональную возможность иметь такие указатели.

В своем проекте RSL я попробовал реализовать такой указатель. Основная идея не нова: необходим объект, который при разрушении будет нотифицировать указатели о факте удаления.

Таким образом мы имеем два класса:

  1. rsl::track::trackable — класс, который оповещает указатели при удалении.
  2. 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, которые позволяют отлавливать подобные проблемы.

Но эти средства имеют свои недостатки:

  1. Работают не везде (не на всех платформах).
  2. Необходима инструментализация кода — код отличается от продакшена.
  3. Нет аналога weak_ptr, указателя котроый зануляется при удалении объекта.
  4. Детектирует время и место доступа по невалидному указателю. Хотя, как правило, более интересен момент, когда происходит удаление объекта, на который есть указатели. По этой же причине инструментация детектирует не все случаи неправильного доступа, а лишь те, которые реально отработали при тестировании.

Надеюсь библиотека окажется полезной С++ разработчикам. Возможно есть другие способы решения подобных проблем, с удовольствием выслушаю в комментариях.

P.S. Еще раз, для удобства, привожу ссылку на код библиотеки RSL.

Автор: lexxmark

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js