В информатике интеллектуальный указатель — это абстрактный тип данных , который имитирует указатель , предоставляя дополнительные функции, такие как автоматическое управление памятью или проверка границ . Такие функции предназначены для уменьшения ошибок , вызванных неправильным использованием указателей, сохраняя при этом эффективность. Интеллектуальные указатели обычно отслеживают память, на которую они указывают, а также могут использоваться для управления другими ресурсами, такими как сетевые подключения и дескрипторы файлов . Интеллектуальные указатели были впервые популяризированы в языке программирования C++ в первой половине 1990-х годов как опровержение критики в отношении отсутствия в C++ автоматической сборки мусора . [1] [2]
Неправильное использование указателей может стать основным источником ошибок. Интеллектуальные указатели предотвращают большинство ситуаций утечек памяти , делая освобождение памяти автоматическим. В более общем смысле, они делают уничтожение объекта автоматическим: объект, управляемый интеллектуальным указателем, автоматически уничтожается ( финализируется и затем освобождается), когда уничтожается последний (или единственный) владелец объекта, например, потому, что владельцем является локальная переменная , и выполнение выходит за пределы области действия переменной . Интеллектуальные указатели также устраняют висячие указатели , откладывая уничтожение до тех пор, пока объект больше не будет использоваться.
Если язык поддерживает автоматическую сборку мусора (например, Java или C# ), тогда интеллектуальные указатели не нужны для освобождения и безопасности управления памятью, но полезны для других целей, таких как управление местонахождением структуры данных кэша и управление ресурсами таких объектов, как как дескрипторы файлов или сетевые сокеты .
Существует несколько типов интеллектуальных указателей. Некоторые работают с подсчетом ссылок , другие — присваивая право собственности на объект одному указателю.
Несмотря на то, что C++ популяризировал концепцию интеллектуальных указателей, особенно их разновидности с подсчетом ссылок , [3] непосредственный предшественник одного из языков, вдохновивших разработку C++, имел встроенные в язык ссылки с подсчетом ссылок. C++ частично был вдохновлен Simula67 . [4] Предком Simula67 была Simula I. Поскольку элемент Simula I аналогичен указателю C++ без null и поскольку процесс Simula I с фиктивным оператором в качестве тела активности аналогичен структуре C++ ( которая сама по себе аналогична записи CAR Hoare в современной на тот момент работе 1960-х годов), ), Simula I имел элементы с подсчетом ссылок (т. е. выражения-указатели, содержащие косвенность) к процессам (т. е. записям) не позднее сентября 1965 года, как показано в цитируемых ниже параграфах. [5]
На процессы можно ссылаться индивидуально. Физически ссылка на процесс — это указатель на область памяти, содержащую локальные для процесса данные и некоторую дополнительную информацию, определяющую его текущее состояние выполнения. Однако по причинам, указанным в разделе 2.2, ссылки на процессы всегда являются косвенными, через элементы, называемые элементами. Формально ссылкой на процесс является значение выражения типа element .
… значения
элементов можно сохранять и извлекать путем присвоения и ссылок на переменные элемента , а также другими способами.
Язык содержит механизм, позволяющий сделать атрибуты процесса доступными извне, т. е. изнутри других процессов. Это называется удаленным доступом. Таким образом, процесс представляет собой структуру данных, на которую можно ссылаться.Стоит отметить сходство между процессом, телом активности которого является фиктивный оператор, и концепцией записи, недавно предложенной К. А. Хоаром и Н. Виртом.
Поскольку C++ заимствовал подход Simula к выделению памяти — новое ключевое слово при выделении процесса/записи для получения нового элемента для этого процесса/записи — неудивительно, что C++ в конечном итоге возродил механизм интеллектуальных указателей Simula с подсчетом ссылок внутри элемента как хорошо.
В C++ интеллектуальный указатель реализован как класс шаблона, который имитирует посредством перегрузки операторов поведение традиционного (необработанного) указателя (например, разыменование, присваивание), обеспечивая при этом дополнительные функции управления памятью.
Интеллектуальные указатели могут облегчить намеренное программирование , выражая в типе то, как будет управляться памятью референта указателя. Например, если функция C++ возвращает указатель, невозможно узнать, должен ли вызывающий объект удалить память референта, когда вызывающий объект закончил обработку информации.
SomeType * AmbigiousFunction (); // Что делать с результатом?
Традиционно для устранения двусмысленности использовались соглашения об именах [6] , что является подверженным ошибкам и трудоемким подходом. В C++11 в этом случае представлен способ обеспечить правильное управление памятью путем объявления функции, возвращающей unique_ptr
,
std :: unique_ptr < SomeType > ObviousFunction ();
Объявление типа возвращаемого значения функции как a unique_ptr
ясно указывает на то, что вызывающая сторона становится владельцем результата, а среда выполнения C++ гарантирует, что память будет освобождена автоматически. До C++11 unique_ptr можно было заменить на auto_ptr , который сейчас устарел.
Чтобы облегчить распределение
std :: shared_ptr <SomeType>
С++ 11 представил:
auto s = std :: make_shared <SomeType> ( конструктор , параметры здесь ) ;
и аналогично
std :: unique_ptr < некоторый_тип >
Начиная с С++ 14, можно использовать:
auto u = std :: make_unique <SomeType> ( конструктор , параметры , здесь ) ;
Почти во всех случаях предпочтительнее использовать эти возможности вместо new
ключевого слова. [7]
C++11 представляет std::unique_ptr
, определенный в заголовке <memory>
. [8]
A unique_ptr
— это контейнер для необработанного указателя, который, unique_ptr
как говорят, принадлежит ему. Явно unique_ptr
предотвращает копирование содержащегося в нем указателя (как это происходит при обычном присваивании), но функцию std::move
можно использовать для передачи владения содержащимся указателем другому объекту unique_ptr
. Невозможно скопировать объект A, unique_ptr
поскольку его конструктор копирования и операторы присваивания явно удалены.
std :: unique_ptr < int > p1 ( new int ( 5 )); std :: unique_ptr <int> p2 = p1 ; // Ошибка компиляции. std :: unique_ptr < int > p3 = std :: move ( p1 ); // Передача права собственности. Теперь p3 владеет памятью, а для p1 установлено значение nullptr. п3 . перезагрузить (); // Удаляет память. п1 . перезагрузить (); // Ничего не делает.
std::auto_ptr
устарел в C++11 и полностью удален из C++17 . Конструктор копирования и операторы присваивания auto_ptr
фактически не копируют сохраненный указатель. Вместо этого они передают его , оставляя предыдущий auto_ptr
объект пустым. Это был один из способов реализации строгого владения, чтобы только один auto_ptr
объект мог владеть указателем в любой момент времени. Это означает, что auto_ptr
его не следует использовать там, где необходима семантика копирования. [9] [ нужна цитация ] Поскольку auto_ptr
он уже существовал со своей семантикой копирования, его нельзя было обновить до указателя, предназначенного только для перемещения, без нарушения обратной совместимости с существующим кодом.
В C++11 представлены std::shared_ptr
и std::weak_ptr
, определенные в заголовке <memory>
. [8] В C++11 также реализовано std::make_shared
( std::make_unique
было введено в C++14) безопасное распределение динамической памяти в парадигме RAII . [10]
A shared_ptr
— это контейнер для необработанного указателя . Он поддерживает подсчет ссылок на содержащийся в нем указатель совместно со всеми копиями файла shared_ptr
. Объект, на который ссылается содержащийся необработанный указатель, будет уничтожен тогда и только тогда, когда все копии объекта shared_ptr
будут уничтожены.
std :: shared_ptr <int> p0 ( new int ( 5 ) ) ; // Допустимо, выделяет 1 целое число и инициализирует его значением 5. std :: shared_ptr < int [] > p1 ( new int [ 5 ]); // Действительно, выделяет 5 целых чисел. std :: shared_ptr < int [] > p2 = p1 ; // Оба теперь владеют памятью. п1 . перезагрузить (); // Память все еще существует благодаря p2. п2 . перезагрузить (); // Освобождает память, поскольку памятью больше никто не владеет.
A weak_ptr
— это контейнер для необработанного указателя. Он создается как копия файла shared_ptr
. Существование или уничтожение weak_ptr
копий shared_ptr
не влияет ни на эту копию, shared_ptr
ни на другие ее копии. После shared_ptr
уничтожения всех копий все weak_ptr
копии становятся пустыми.
std :: shared_ptr <int> p1 = std :: make_shared <int> ( 5 ) ; std :: weak_ptr <int> wp1 { p1 } ; // p1 владеет памятью. { std :: shared_ptr <int> p2 = wp1 . замок (); // Теперь p1 и p2 владеют памятью. // p2 инициализируется слабым указателем, поэтому вам нужно проверить, существует ли // память! если ( р2 ) { DoSomethingWith ( р2 ); } } // p2 уничтожается. Память принадлежит p1. п1 . перезагрузить (); // Освободить память. std :: shared_ptr <int> p3 = wp1 . замок (); // Памяти больше нет, поэтому мы получаем пустой файлshared_ptr. if ( p3 ) { // код не выполнит ActionThatNeedsALivePointer ( p3 ); }
Поскольку реализация shared_ptr
использует подсчет ссылок , циклические ссылки могут стать потенциальной проблемой. Круговую shared_ptr
цепочку можно разорвать, изменив код так, чтобы одна из ссылок была weak_ptr
.
Несколько потоков могут безопасно одновременно обращаться к разным объектам shared_ptr
и weak_ptr
объектам, указывающим на один и тот же объект. [11]
Объект, на который ссылаются, должен быть защищен отдельно, чтобы обеспечить потокобезопасность .
shared_ptr
и weak_ptr
основаны на версиях, используемых библиотеками Boost . [ нужна цитация ] Технический отчет C++ 1 (TR1) впервые представил их в стандарте как общие утилиты , но в C++11 добавлено больше функций, в соответствии с версией Boost.
Существуют и другие типы интеллектуальных указателей (которые не входят в стандарт C++), реализованные в популярных библиотеках C++ или специальных STL . Некоторые примеры включают указатель опасности [12] и интрузивный указатель. [13] [14]