Черепной указатель
- 1 year ago
- 0
- 0
Умный указатель ( англ. smart pointer ) — идиома косвенного обращения к памяти, которая широко используется при программировании на высокоуровневых языках как: C++ , Rust и так далее. Как правило, реализуется в виде специализированного класса (обычно — параметризованного ), имитирующего интерфейс обычного указателя и добавляющего необходимую новую функциональность (например — проверку границ при доступе или очистку памяти ) .
Как правило, основной целью задействования умных указателей является инкапсуляция работы с динамической памятью таким образом, чтобы свойства и поведение умных указателей имитировали свойства и поведение обычных указателей. При этом на них возлагается обязанность своевременного и аккуратного высвобождения выделенных ресурсов, что упрощает разработку кода и процесс отладки, исключая утечки памяти и возникновение висячих ссылок .
Такие обычно используются с объектами, имеющими специальные операции «увеличить число ссылок» (
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
).
и
;
SmartPtr
;
QSharedPointer
,
QWeakPointer
и др.
Часто указатели совместного владения слишком большие и «тяжёлые» для задач программиста: например, нужно создать объект одного из N типов, владеть им, время от времени обращаясь к его виртуальным функциям, а потом корректно удалить. Для этого используется «младший брат» — указатель единоличного владения.
Такие указатели при присвоении нового значения или удалении сами удаляют объект. Присвоение указателей единоличного владения возможно только с разрушением одного из указателей — таким образом, никогда не будет ситуации, что два указателя владеют одним объектом.
Их недостатком являются трудности с передачей объекта за пределы области видимости указателя.
(начиная с C++11 не рекомендуется),
std::unique_ptr
.
В большинстве случаев, если есть функция, имеющая дело с массивом, пишут одно из двух:
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);
};
Нередко используется для строк: синтаксическим разборам , обеспечению жизнедеятельности текстового редактора и прочим специфичным задачам нужны собственные структуры данных, более быстрые, чем стандартные методы работы со строками.
,
.
QStringView
.