В информатике интеллектуальный указатель - это абстрактный тип данных, который имитирует указатель, обеспечивая при этом дополнительные функции, такие как автоматическое управление памятью или проверка границ. Такие функции предназначены для уменьшения количества ошибок вызвал неправильным использованием указателей при сохранении эффективности. Интеллектуальные указатели обычно отслеживают память, на которую они указывают, а также могут использоваться для управления другими ресурсами, такими как сетевые соединения и дескрипторы файлов. Интеллектуальные указатели были впервые популяризированы в языке программирования C ++ в первой половине 1990-х годов как опровержение критики отсутствия в C ++ автоматической сборки мусора.
Неправильное использование указателей может быть основным источником ошибок. Интеллектуальные указатели предотвращают большинство ситуаций утечек памяти, делая освобождение памяти автоматическим. В более общем смысле, они делают разрушение объекта автоматическим: объект, управляемый интеллектуальным указателем, автоматически уничтожается (finalized, а затем освобождается), когда уничтожается последний (или единственный) владелец объекта., например, потому что владельцем является локальная переменная, а выполнение оставляет переменную в области видимости. Умные указатели также устраняют висячие указатели, откладывая уничтожение до тех пор, пока объект не перестанет использоваться.
Если язык поддерживает автоматическую сборку мусора (например, Java или C # ), то интеллектуальные указатели не нужны для аспектов восстановления и безопасности управления памятью, однако полезны для других целей, таких как управление размещением структуры данных кеша и управление ресурсами таких объектов, как дескрипторы файлов или сетевые сокеты.
. существуют типы умных указателей. Некоторые работают с подсчетом ссылок, другие путем присвоения права собственности на объект одному указателю.
Несмотря на то, что C ++ популяризировал концепцию интеллектуальных указателей, особенно разновидность подсчитываемых ссылок, непосредственного предшественника одного из языков, которые вдохновили C ++ дизайн имел ссылки с подсчетом ссылок, встроенные в язык. C ++ частично был вдохновлен Simula67. Предком Simula67 была Simula I. Постольку, поскольку элемент Simula I аналогичен указателю C ++ без нуля, и поскольку процесс Simula I с фиктивным оператором, поскольку его тело действия аналогично структуре C ++ (которая сама аналогична записи CAR Hoare в то время) - современная работа 1960-х годов), в Simula I были элементы с подсчетом ссылок (т. е. выражения-указатели, которые содержат косвенное указание) на процессы (т. е. записи) не позднее сентября 1965 г., как показано в приведенных ниже абзацах.
На процессы можно ссылаться. индивидуально. Физически ссылка на процесс - это указатель на область памяти, содержащую локальные для процесса данные и некоторую дополнительную информацию, определяющую его текущее состояние выполнения. Однако по причинам, указанным в разделе 2.2, ссылки на процессы всегда косвенные, через элементы, называемые элементами. Формально ссылка на процесс - это значение выражения типа element... ….. значения элементов могут быть сохранены и извлечены путем присвоений и ссылок на переменные элемента и другими способами... Язык содержит механизм для обеспечение доступа к атрибутам процесса извне, т. е. изнутри других процессов. Это называется удаленным доступом. Таким образом, процесс является структурой данных, на которую можно ссылаться...
Стоит отметить сходство между процессом, тело действия которого является фиктивным оператором, и концепцией записи, недавно предложенной CAR Hoare и N. Wirth
Поскольку C ++ заимствовал Подход Simula к распределению памяти - ключевое слово new при выделении процесса / записи для получения нового элемента для этого процесса / записи - неудивительно, что C ++ в конечном итоге воскресил в Simula механизм интеллектуального указателя с подсчетом ссылок внутри элемента. также.
В C ++ интеллектуальный указатель реализован как класс шаблона, который имитирует с помощью перегрузки оператора поведение традиционный (необработанный) указатель (например, разыменование, присваивание), обеспечивая при этом дополнительные функции управления памятью.
Интеллектуальные указатели могут облегчить преднамеренное программирование, выражая в типе то, как будет управляться память референта указателя. Например, если функция C ++ возвращает указатель, невозможно узнать, должен ли вызывающий объект удалить память референта, когда вызывающий закончит с информацией.
SomeType * AmbiguousFunction (); // Что делать с результатом?
Традиционно для устранения неоднозначности использовались соглашения об именах, что является трудоемким подходом, подверженным ошибкам. C ++ 11 представил способ гарантировать правильное управление памятью в этом случае, объявив функцию, возвращающую unique_ptr
,
std :: unique_ptrObviousFunction ();
Объявление типа возвращаемого значения функции как unique_ptr
явно указывает на то, что вызывающий объект становится владельцем результата, а среда выполнения C ++ гарантирует, что память будет освобождена автоматически. До C ++ 11, unique_ptr можно было заменить на auto_ptr.
Для упрощения выделения
std :: shared_ptr
C ++ 11 введено:
auto s = std :: make_shared(конструктор, параметры, здесь);
и аналогично
std :: unique_ptr
Начиная с C ++ 14 можно использовать:
auto u = std :: make_unique(конструктор, параметры, здесь) ;
Практически во всех случаях предпочтительно использовать эти возможности вместо нового
ключевого слова :
C ++ 11 вводит std: : unique_ptr
, определенный в заголовке
.
A unique_ptr
, является контейнером для необработанного указателя, которым, как говорят, принадлежит unique_ptr
. unique_ptr
явно предотвращает копирование содержащегося в нем указателя (как это произошло бы при обычном назначении), но функцию std :: move
можно использовать для передачи владения содержащимся указателем другому unique_ptr
. unique_ptr
не может быть скопирован, поскольку его конструктор копирования и операторы присваивания явно удалены.
std :: unique_ptrp1 (новый int (5)); std :: unique_ptr p2 = p1; // Ошибка компиляции. std :: unique_ptr 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
не следует использовать там, где требуется семантика копирования. Поскольку auto_ptr
уже существовал с его семантикой копирования, его нельзя было обновить до указателя только для перемещения без нарушения обратной совместимости с существующим кодом.
В C ++ 11 представлены std :: shared_ptr
и std :: weak_ptr
, определенные в заголовке
. C ++ 11 также представляет std :: make_shared
(std :: make_unique
был представлен в C ++ 14) для безопасного выделения динамической памяти в парадигме RAII.
A shared_ptr
- это контейнер для необработанного указателя. Он поддерживает подсчет ссылок владение содержащимся в нем указателем во взаимодействии со всеми копиями shared_ptr
. Объект, на который ссылается содержащийся в нем необработанный указатель, будет уничтожен тогда и только тогда, когда будут уничтожены все копии shared_ptr
.
std :: shared_ptrp0 (новый int (5)); // Допустимо, выделяет 1 целое число и инициализирует его значением 5. std :: shared_ptr p1 (new int [5]); // Допустимо, выделяет 5 целых чисел. std :: shared_ptr p2 = p1; // Теперь память принадлежит обоим. p1.reset (); // Память все еще существует из-за p2. p2.reset (); // Освобождает память, так как никто другой не владеет памятью.
A weak_ptr
- контейнер для необработанного указателя. Он создается как копия shared_ptr
. Существование или уничтожение weak_ptr
копий shared_ptr
не влияет на shared_ptr
или другие его копии. После того, как все копии shared_ptr
были уничтожены, все копии weak_ptr
станут пустыми.
std :: shared_ptrp1 = std :: make_shared (5); std :: weak_ptr wp1 {p1}; // p1 владеет памятью. {std :: shared_ptr p2 = wp1.lock (); // Теперь p1 и p2 владеют памятью. // p2 инициализируется слабым указателем, поэтому вы должны проверить, // существует ли еще память! если (p2) {DoSomethingWith (p2); }} // p2 уничтожен. Память принадлежит p1. p1.reset (); // Освободить память. std :: shared_ptr p3 = wp1.lock (); // Памяти больше нет, поэтому мы получаем пустой shared_ptr. if (p3) {// код не выполнит ActionThatNeedsALivePointer (p3); }
Поскольку реализация shared_ptr
использует подсчет ссылок, циклические ссылки потенциально представляют проблему. Круговую цепочку shared_ptr
можно разорвать, изменив код так, чтобы одна из ссылок была weak_ptr
.
. Несколько потоков могут безопасно одновременно обращаться к разным shared_ptr
и weak_ptr
объекты, которые указывают на один и тот же объект.
Указанный объект должен быть защищен отдельно, чтобы гарантировать, что потокобезопасность.
shared_ptr
и weak_ptr
основаны на версии, используемые библиотеками Boost. Технический отчет C ++ 1 (TR1) сначала представил их в стандарте, как общие утилиты, но C ++ 11 добавляет больше функции, соответствующие версии Boost.