stringtranslate.com

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

В информатике умный указатель — это абстрактный тип данных , который имитирует указатель , предоставляя при этом дополнительные возможности, такие как автоматическое управление памятью или проверка границ . Такие возможности предназначены для уменьшения ошибок, вызванных неправильным использованием указателей, при сохранении эффективности. Умные указатели обычно отслеживают память, на которую они указывают, и могут также использоваться для управления другими ресурсами, такими как сетевые соединения и дескрипторы файлов . Умные указатели впервые были популяризированы в языке программирования 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, ссылки на процессы всегда являются косвенными, через элементы, называемые элементами. Формально ссылка на процесс — это значение выражения типа элемент .
… значения
элементов могут храниться и извлекаться с помощью назначений и ссылок на переменные элементов и другими способами.
Язык содержит механизм, позволяющий сделать атрибуты процесса доступными извне, т. е. изнутри других процессов. Это называется удаленным доступом. Таким образом, процесс — это ссылочная структура данных.

Стоит отметить сходство между процессом, телом деятельности которого является фиктивное утверждение, и концепцией записи, недавно предложенной К. А. Хоаром и Н. Виртом.

Поскольку C++ заимствовал подход Simula к выделению памяти ( новое ключевое слово при выделении процесса/записи для получения нового элемента для этого процесса/записи), неудивительно, что C++ в конечном итоге возродил механизм интеллектуальных указателей Simula с подсчетом ссылок и внутри элемента .

Функции

В C++ интеллектуальный указатель реализован как шаблонный класс, который имитирует посредством перегрузки операторов поведение традиционного (сырого) указателя (например, разыменование, присваивание), предоставляя при этом дополнительные функции управления памятью.

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

SomeType * AmbiguousFunction (); // Что делать с результатом?  

Традиционно для разрешения неоднозначности использовались соглашения об именах, [6] что является подверженным ошибкам и трудоемким подходом. C++11 представил способ обеспечения правильного управления памятью в этом случае, объявив функцию возвращающей unique_ptr,

std :: unique_ptr < SomeType > ObviousFunction (); 

Объявление типа возвращаемого функцией значения как a unique_ptrделает явным тот факт, что вызывающая сторона берет на себя владение результатом, а среда выполнения C++ гарантирует, что память будет автоматически освобождена. До C++11 unique_ptr можно было заменить на auto_ptr , который теперь устарел.

Создание новых объектов

Для облегчения распределения

std :: shared_ptr < SomeType >

В C++11 представлены:

auto s = std :: make_shared < SomeType > ( конструктор , параметры , здесь );     

и аналогично

std :: unique_ptr < some_type >

Начиная с C++14 можно использовать:

auto u = std :: make_unique < SomeType > ( конструктор , параметры , здесь );     

Почти во всех случаях предпочтительнее использовать эти возможности вместо newключевого слова. [7]

уникальный_ptr

C++11 вводит std::unique_ptr, определенный в заголовке <memory>. [8]

A unique_ptr— это контейнер для необработанного указателя, которым, unique_ptrкак говорят, владеет . A 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.          p3 . reset (); // Удаляет память. p1 . reset (); // Ничего не делает.  

std::auto_ptrустарело в C++11 и полностью удалено из C++17 . Конструктор копирования и операторы присваивания auto_ptrна самом деле не копируют сохраненный указатель. Вместо этого они передают его , оставляя предыдущий auto_ptrобъект пустым. Это был один из способов реализации строгого владения, так что только один auto_ptrобъект может владеть указателем в любой момент времени. Это означает, что его auto_ptrне следует использовать там, где требуется семантика копирования. [9] [ необходима цитата ] Поскольку auto_ptrон уже существовал со своей семантикой копирования, его нельзя было обновить до указателя только для перемещения, не нарушив обратную совместимость с существующим кодом.

shared_ptr и weak_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 ; // Теперь оба владеют памятью.          p1 . reset (); // Память все еще существует из-за p2. p2 . reset (); // Освобождает память, поскольку больше никто ею не владеет.  

A weak_ptr— это контейнер для необработанного указателя. Он создается как копия a shared_ptr. Существование или уничтожение weak_ptrкопий a shared_ptrне влияет на или другие его копии. После уничтожения shared_ptrвсех копий a все копии становятся пустыми.shared_ptrweak_ptr

std :: shared_ptr < int > p1 = std :: make_shared < int > ( 5 ); std :: weak_ptr < int > wp1 { p1 }; // p1 владеет памятью.      { std :: shared_ptr < int > p2 = wp1 . lock (); // Теперь памятью владеют p1 и p2. // p2 инициализируется слабым указателем, поэтому вам нужно проверить, // существует ли еще память! if ( p2 ) { DoSomethingWith ( p2 ); } } // p2 уничтожен. Память принадлежит p1.            p1 . reset (); // Освобождаем память. std :: shared_ptr < int > p3 = wp1 . lock (); // Память закончилась, поэтому мы получаем пустой 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]

Смотрите также

Ссылки

  1. ^ Клайн, Маршалл (сентябрь 1997 г.). «Разделы C++ FAQ Lite по интеллектуальным указателям с подсчетом ссылок и семантике ссылок копирования при записи в FAQ по управлению свободным хранилищем». cis.usouthal.edu . Получено 06.04.2018 .
  2. ^ Колвин, Грегори (1994). «Предложение о стандартизации counted_ptr в стандартной библиотеке C++» (PDF) . open-std.org . Получено 06.04.2018 .
  3. ^ Клабник, Стив; Николс, Кэрол (2023) [2018]. «15. Умные указатели». Язык программирования Rust (2-е изд.). Сан-Франциско, Калифорния, США: No Starch Press, Inc. стр. 315–351. ISBN 978-1-7185-0310-6.(xxix+1+527+3 страницы)
  4. ^ Страуструп, Бьярне. "История C++: 1979–1991" (PDF) . Получено 2018-04-06 .
  5. ^ Даль, Оле-Йохан; Найгаард, Кристен (сентябрь 1966 г.). «SIMULA — язык моделирования на основе ALGOL» (PDF) . фолк.уйо.но. ​Проверено 6 апреля 2018 г.
  6. ^ «Руководство Taligent по проектированию программ, раздел Использование специальных имен для процедур копирования, создания и принятия».
  7. ^ Sutter, Herb (2013-04-20). "Отчет о поездке: весенняя встреча ISO C++ 2013". isocpp.org . Получено 2013-06-14 .
  8. ^ согласно ISO 14882:2011 20.7.1
  9. ^ CERT C++ Стандарт безопасного кодирования
  10. ^ ИСО 14882:2014 20.7.1
  11. ^ "boost::shared_ptr безопасность потока".(Примечание. Формально не охватывает std::shared_ptr, но считается, что имеет те же ограничения потоковой передачи.)
  12. ^ "folly/Hazptr.h на главной · facebook/folly". github.com .
  13. ^ "Boost.SmartPtr: Библиотека интеллектуальных указателей - 1.81.0". boost.org .
  14. ^ "EASTL/intrusive_ptr.h в master · electronicarts/EASTL". github.com .

Дальнейшее чтение

Внешние ссылки