Понимание интеллектуальных указателей с использованием более совершенной структуры

Вы можете прочитать эту статью в моем собственном блоге, если вы предпочитаете ее средней.

В 2011 году стандарт C ++ представил атомарные аналоги unique_ptr, shared_ptr, weak_ptr и shared_ptr. Все основные распространители стандартной библиотеки немедленно и безупречно реализовали все шесть этих указателей.

К началу 2012 года каждый разработчик C ++ быстро ознакомился с этими новыми интеллектуальными указателями и понял их важность и варианты использования. За этим быстро последовал массовый рефакторинг большинства библиотек и проектов C ++ с открытым исходным кодом, чтобы свести на нет использование необработанных указателей. Вскоре их примеру последовали частные компании, личные кодовые базы и учебные материалы.

В 2018 году человек, изучающий C ++, вряд ли узнает, что такое необработанный указатель, пока не проработает с языком несколько месяцев. Они по-прежнему используются в различных пограничных случаях, таких как некоторые высокопроизводительные структуры данных и для различных уровней совместимости. Однако большинство приложений перешли на использование только интеллектуальных указателей и при этом используют их правильно. Таким образом, наш код становится более безопасным, его легче читать, изменять и обдумывать.

Так что же пошло не так?

Что ж, много чего пошло не так, у меня нет времени здесь все повторять. Тем не менее, я хочу сказать вам, дорогой читатель, что одна из причин, по которой интеллектуальные указатели используются недостаточно, а shared_ptr часто злоупотребляют, заключается в том, что мы неправильно поняли основную концепцию, лежащую в основе них.

Умные указатели - это право собственности

Многие люди, кажется, думают об уникальных указателях как о механизме, освобождающем от необработанных указателей. Еще больше людей рассматривают shared_ptr как указатель C ++ на «сборку мусора», который можно использовать и злоупотреблять так же, как обращение со ссылкой на объект JVM. Умные указатели действительно освобождают ресурс, когда он больше не нужен, однако важной частью этого предложения является не «бесплатно», а «когда он больше не нужен».

Умные указатели представляют собой способ выразить владение ресурсом. Это инструмент, который помогает нам и будущим разработчикам понять, как мы используем ресурс. Поступая так, они также помогают нам избежать простоя в использовании и утечек памяти, но эти вещи являются побочным эффектом их истинной цели.

Уникальный указатель

Std :: unique_ptr практически не имеет накладных расходов и имеет очень предсказуемое поведение. Базовый ресурс уничтожается, когда он существует в области видимости, его можно переместить, но нельзя скопировать. Он предназначен для представления ресурса, которым владеет одна подпрограмма, но который необходимо динамически распределять. Случаи использования включают необходимость перемещать дорогостоящий ресурс для копирования с помощью not mctor, передачу ресурса между потоками, различные реализации синглтонов или pimpl и совместимость с библиотеками C, которые используют указатели.

Этот указатель особенно хорош, потому что к нему можно применить те же соображения, что и к значению, объявленному в стеке. Он будет активно препятствовать тому, чтобы вы начали использовать его после бесплатных ошибок или по ошибке поделиться им с другими потоками.

Общий указатель

Это немного сложнее. Это дорого и опасно использовать, поскольку он выражает право собственности гораздо более «свободным» способом. Он предназначен в основном для многопоточного совместного использования ресурсов и дает вам гарантию того, что базовый ресурс не был освобожден другим потоком. Этот указатель особенно хорош, если у вас есть функция или метод, который должен возвращать общий ресурс, поскольку вызывающий сразу же безумно осознает это, его также можно использовать для реализации множества механизмов совместного использования состояния более высокого уровня.

Самая большая проблема общего указателя заключается в том, что он часто используется вместо уникального указателя или слабого указателя, что приводит к путанице в коде и низкой производительности.

Слабый указатель

Наконец, мы подошли к std :: weak_ptr, вероятно, наиболее малоиспользуемому из трех. Этот указатель следует использовать для ссылки на общий ресурс, срок жизни которого не контролируется владельцем указателя. Это может быть весьма полезно, когда внешняя сущность желает раскрыть объект, не передавая вам полное владение указанным ресурсом.

Очевидная проблема с этим указателем заключается в том, что у людей может возникнуть соблазн немедленно превратить его в общий указатель и продолжать его использовать, вместо того, чтобы освобождать владение после каждого использования. Удобно, что наиболее очевидный шаблон использования этого указателя не позволит людям сделать именно это:

По сути, этот указатель ближе всего к необработанному указателю. Тем не менее, он предоставляет интеллектуальный механизм безопасности, заставляющий проверку nullptr в сочетании с временным совместным владением, когда вы хотите использовать ресурс, который он хранит. Таким образом, вы не можете неосознанно использовать висящий указатель.

Наконец, немного кода

Одна вещь, которая меня удивляет, - это то, как мало примеров кода, сравнивающих использование необработанных указателей с использованием интеллектуальных указателей, существует. Итак, вот пример, иллюстрирующий, как все три семантики владения помогают по сравнению с подходом с необработанным указателем:

Итак, очевидный ответ заключается в том, что мы должны прочитать документацию этой библиотеки виджетов и понять, как использовать get_widget. Однако документация может быть неправильной, у нее нет компилятора, чтобы проверить ее. Документация также может отсутствовать, устареть или мы можем просто неверно истолковать ее из-за множества факторов.

Что, если бы у автора библиотеки был способ сообщить об использовании предполагаемых шаблонов использования для этого виджета через то, что мы (разработчик C ++) обязаны прочитать и понять? Интерфейс самой библиотеки. Посмотрим, как это пойдет:

Выставка

Предположим, что как только мы запрашиваем виджет, мы можем его использовать и злоупотреблять. Библиотека дает нам указатель, а не сам объект по причинам, не связанным с правом собственности.

В этом случае мы просто использовали бы unique_ptr.

Приложение B

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

Приложение C

Что, если я вообще не хочу, чтобы пользователь виджета действительно «владел» виджетом? Моя библиотека должна решить, когда и если виджет будет уничтожен ... в разумных пределах, конечно (например, не уничтожать его, пока он используется).

Вот и все, простые и простые примеры того, как семантика владения представлена ​​через 3 типа интеллектуальных указателей.

Очевидно, что интеллектуальные указатели не охватывают все крайние случаи, особенно когда речь идет об эффективном управлении ресурсами в незаблокированном коде. Однако они охватывают большинство распространенных.

На мой взгляд, именно так мы должны смотреть на интеллектуальные указатели через призму владения, а не через RAII.

Если вам понравилась эта статья, вам также могут понравиться: