Умный указатель: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
[непроверенная версия][непроверенная версия]
Содержимое удалено Содержимое добавлено
Нет описания правки
Строка 24: Строка 24:
}}</ref>.
}}</ref>.


== Функционирование ==
== Разновидности ==
Как правило, основной целью задействования умных указателей является [[Инкапсуляция (программирование)|инкапсуляция]] работы с [[динамическая память|динамической памятью]] таким образом, чтобы свойства и поведение умных указателей имитировали свойства и поведение обычных указателей. При этом на них возлагается обязанность своевременного и аккуратного высвобождения выделенных ресурсов, что упрощает разработку кода и процесс отладки, исключая [[утечка памяти|утечки памяти]] и возникновение [[висячая ссылка|висячих ссылок]]<ref>{{книга
Чаще всего умный указатель [[Инкапсуляция (программирование)|инкапсулирует]] [[семантика|семантику]] [[Получение ресурса есть инициализация|владения ресурсом]]. В таком случае он называется ''владеющим указателем''.
| автор = Ivor Horton, Peter Van Weert

| часть = Raw Pointers and Smart Pointers
Владеющие указатели применяются для борьбы с утечками памяти и висячими ссылками. [[Утечка памяти|Утечкой памяти]] называется ситуация, когда в программе нет ни одного указателя, хранящего адрес объекта, созданного в динамической памяти. [[Висячая ссылка|Висячей ссылкой]] называется указатель, ссылающийся на уже удалённый объект. Семантика владения для динамически созданных объектов означает, что удаление или присвоение нового значения указателю будет согласовано с временем жизни объекта.
| ссылка часть =
| заглавие = Beginning C++17. From Novice to Professional
| оригинал =
| ссылка =
| викитека =
| ответственный =
| издание = 5-е
| место =
| издательство = Apress
| год = 2018
| volume =
| pages = 206
| columns =
| allpages =
| серия =
| isbn = 978-1-4842-3365-8
| doi =
| тираж =
| ref =
}}</ref>.


=== Указатели совместного владения (с подсчётом ссылок) ===
=== Указатели совместного владения (с подсчётом ссылок) ===

Версия от 08:55, 23 июня 2018

Умный указатель (англ. smart pointer) — идиома косвенного обращения к памяти, которая широко используется при программировании на языке высокого уровня C++. Как правило, реализуется в виде специализированного класса (обычно — параметризованного), имитирующего интерфейс обычного указателя и добавляющего необходимую новую функциональность (например — проверку границ при доступе или очистку памяти)[1].

Функционирование

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

Указатели совместного владения (с подсчётом ссылок)

Такие обычно используются с объектами, имеющими специальные операции «увеличить число ссылок» (AddRef() в COM) и «уменьшить число ссылок» (Release() в COM). Чаще всего такие объекты унаследованы от специального класса или интерфейса (например, IUnknown в COM).

При появлении новой ссылки на объект вызывается операция «увеличить число ссылок», а при уничтожении — «уменьшить число ссылок». Если в результате операции «уменьшить число ссылок» число ссылок на объект становится равным нулю, то объект удаляется.

Такая методика называется автоматическим подсчётом ссылок. Она согласует число указателей, хранящих адрес объекта, с числом ссылок, хранящимся в объекте, а при достижении этим числом нулевого значения приводит к удалению объекта. Её преимуществами являются относительно высокие надёжность, быстродействие и простота реализации в C++. Недостатком является усложнение использования в случае возникновения циклических ссылок (необходимость пользоваться «слабыми ссылками»).

Реализации

Существуют два вида таких указателей: с хранением счётчика внутри объекта и с хранением счётчика снаружи.

Самый простой из вариантов — хранение счётчика внутри управляемого объекта. В COM объекты с подсчётом ссылок реализуются следующим образом:

  • Объект обязан хранить внутри себя неотрицательное целое, которое означает число внешних указателей, ссылающихся на этот объект.
  • При присваивании указателю адреса нового объекта указатель вызывает у объекта метод AddRef(). Если перед этим указатель ссылался на другой объект, то сначала вызывается метод Release() прежнего объекта. При удалении указателя (выходе его из области видимости или разрушении объекта, полем которого он являлся), если этот указатель ссылается на существующий объект, указатель вызывает метод Release() объекта.
  • Реализация метода Release() каждый раз уменьшает число ссылок на единицу и сразу проверяет новое значение. Если число ссылок стало равно нулю, метод Release() вызывает удаление объекта.

Сходным образом реализован boost::intrusive_ptr.

В std::shared_ptr счётчики ссылок хранятся снаружи объекта, в специальной структуре данных. Такой умный указатель вдвое больше стандартного (в нём два поля, одно указывает на структуру-счётчик, второе — на управляемый объект). Такая конструкция позволяет:

  • Слабые указатели — указатели, которые не удерживают объекта и позволяют ему исчезать, как только пропадёт последний «сильный» указатель. Структура-счётчик удерживается, пока действует хоть один слабый указатель на пропавший объект.
  • Указатели, которые указывают на один объект, но управляют другим (например, управляют созданным через std::make_shared объектом, но указывают на его поле).
  • Умные указатели, которые ничем не управляют, только указывают (например, на глобальный объект, который живёт всё время, пока работает программа).

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

Проблема циклических ссылок

Предположим, есть два объекта и в каждом из них по владеющему указателю. Указателю в первом объекте присвоим адрес второго объекта, а указателю во втором — адрес первого объекта. Если теперь всем внешним (то есть не хранящимся внутри этих объектов) указателям на два данных объекта присвоить новые значения, то указатели внутри объектов по-прежнему будут владеть друг другом и будут оставаться в памяти. В результате возникнет ситуация, когда к объектам невозможно получить доступ, то есть утечка памяти.

Проблема циклических ссылок решается либо путём соответствующего проектирования структур данных, либо использованием сборки мусора, либо использованием двух видов ссылок: сильные (владеющие) и слабые (невладеющие, напр. std::weak_ptr).

Примеры реализаций

  • boost: boost::shared_ptr и boost::intrusive_ptr;
  • Loki: SmartPtr;
  • Поддержка COM, реализованная в большинстве компиляторов Си++ для Windows;
  • Qt: QSharedPointer, QWeakPointer и др.

Указатели единоличного владения

Часто указатели совместного владения слишком большие и «тяжёлые» для задач программиста: например, нужно создать объект одного из N типов, владеть им, время от времени обращаясь к его виртуальным функциям, а потом корректно удалить. Для этого используется «младший брат» — указатель единоличного владения.

Такие указатели при присвоении нового значения или удалении сами удаляют объект. Присвоение указателей единоличного владения возможно только с разрушением одного из указателей — таким образом, никогда не будет ситуации, что два указателя владеют одним объектом.

Их недостатком являются трудности с передачей объекта за пределы области видимости указателя.

Примеры реализаций

Указатели на чужой буфер памяти

В большинстве случаев, если есть функция, имеющая дело с массивом, пишут одно из двух:

void sort(size_t size, int* data);    // указатель + размер
void sort(std::vector<int>& data);    // конкретная структура памяти

Первое исключает автоматическую проверку диапазона. Второе ограничивает применимость std::vector’ом, и нельзя отсортировать, например, строку массива или часть другого vector’а.

Потому в развитых библиотеках для функций, которые пользуются чужими буферами памяти, используют «лёгкие» типы данных наподобие

template <class T>
struct Buf1d {
  T* data;
  size_t size;

  Buf1d(std::vector<T>& vec);
  T& operator [](size_t i);
};

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

Примеры реализаций

  • стандартная библиотека шаблонов: std::string_view.
  • Qt: QStringView.

Примечания

  1. Элджер Д. Умные указатели как идиома // С++. Библиотека программиста. — 1999. — С. 75. — 320 с. — ISBN 0-12-049942-8.
  2. Ivor Horton, Peter Van Weert. Raw Pointers and Smart Pointers // Beginning C++17. From Novice to Professional. — 5-е. — Apress, 2018. — P. 206. — ISBN 978-1-4842-3365-8.

Ссылки