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, ссылки на процессы всегда являются косвенными, через элементы, называемые элементами. Формально ссылкой на процесс является значение выражения типа 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]

unique_ptr

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он уже существовал со своей семантикой копирования, его нельзя было обновить до указателя, предназначенного только для перемещения, без нарушения обратной совместимости с существующим кодом.

общий_ptr и слабый_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]

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

Рекомендации

  1. ^ Клайн, Маршалл (сентябрь 1997 г.). «Часто задаваемые вопросы по C++ Lite, посвященные интеллектуальным указателям с подсчетом ссылок и семантике ссылок при копировании при записи в часто задаваемых вопросах по управлению бесплатным хранилищем». cis.usouthal.edu . Проверено 6 апреля 2018 г.
  2. ^ Колвин, Грегори (1994). «Предложение по стандартизации counted_ptr в стандартной библиотеке C++» (PDF) . open-std.org . Проверено 6 апреля 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) . Проверено 6 апреля 2018 г.
  5. ^ Даль, Оле-Йохан; Найгаард, Кристен (сентябрь 1966 г.). «SIMULA — язык моделирования на основе ALGOL» (PDF) . фолк.уйо.но. ​Проверено 6 апреля 2018 г.
  6. ^ «Руководство Taligent по разработке программ, раздел «Используйте специальные имена для копирования, создания и внедрения процедур».
  7. ^ Саттер, Херб (20 апреля 2013 г.). «Отчет о поездке: встреча ISO C++ весной 2013 г.». isocpp.org . Проверено 14 июня 2013 г.
  8. ^ ab ISO 14882:2011 20.7.1
  9. ^ CERT Стандарт безопасного кодирования C++
  10. ^ ИСО 14882:2014 20.7.1
  11. ^ «Потокобезопасность boost::shared_ptr».(Примечание. Формально не распространяется на std::shared_ptr, но считается, что он имеет те же ограничения на потоки.)
  12. ^ "глупость/Hazptr.h на главной · Facebook/глупость" . github.com .
  13. ^ "Boost.SmartPtr: Библиотека интеллектуальных указателей - 1.81.0" . boost.org .
  14. ^ "EASTL/intrusive_ptr.h в мастере · Electronicarts/EASTL" . github.com .

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

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