====== Зачем это нужно ======
Ни для кого не секрет, что работа с динамической памятью и системными ресурсами вообще является одним из самых сложных аспектов при программировании на С++. Сложность этой проблемы заключается в том, что память и ресурсы имеют обыкновение утекать… Безвозвратно и бесконтрольно. И если синтаксические и логические ошибки выловить в программе достаточно просто, то на поиск утечек памяти уходит не одна неделя. За все время существования этой проблемы разработчиками было придумано множество путей облегчения себе жизни. Кто-то, избрав путь наименьшего сопротивления, перешел на такие языки, как Java или C#, где за ресурсами следить вроде как не надо – на это существуют сборщики мусора, а динамическая память (в понимании С++) отсутствует как класс. Кто-то вооружившись BoundsChecker’ом анализирует результаты работы программы с целью выявить места, в которых происходит утечка. Кто-то, набравшись терпения, пишет собственные менеджеры памяти и сборщики мусора для С++. А кто-то, взяв на вооружения некоторые идеи языка С++, разрабатывает т. н. ведущие указатели, дабы, оставаясь в рамках языка, исключить саму возможность утечек.
Что есть ведущий указатель (или, иначе говоря Smart Pointer)? Это объект, поведение которого не отличается (ну, или почти не отличается) от поведения обычного указателя, но обладающий при этом рядом достоинств, отсутствующих у обычного указателя. Вообще говоря, по этому поводу написано столько, что писать еще раз тоже самое – большого смыла нет. Достаточно лишь упомянуть вот о чем. Ведущие (или умные) указатели обычно подразделяются на два основных типа – «слабые» и с «подсчетом ссылок». Слабые указатели позволяют без лишних затрат освободить хранящийся в них объект по выходу из блока, в котором такой указатель был объявлен. Тем самым решается проблема «потерянных ссылок», когда разработчик, по невнимательности, забывает освобождать динамическую память, указатель на которую сохраняется на стеке:
void SomeFunc()
{
char* buff = new char[16];
_itoa(125, buff, 10);
}
В этом примере буфер был выделен, но не был удален.
Указатели же с подсчетом ссылок позволяют контролировать объект в течение гораздо более длительного срока. Достигается это тем, что такой указатель «считает ссылки» на указываемый объект и удаляет только тогда, когда контролируемый объект уже никому не нужен. Пример:
Объявляется класс:
class SomeClass
{
public:
SomeClass() {m_SomeObject = new int;}
~SomeClass() {delete m_SomeObject;}
private:
int *m_SomeObject;
};
// потом где-то в коде имеет место такая конструкция:
SomeClass obj1;
{
SomeClass obj2(obj1);
// Что-то делаем с объектом obj1
}
// Что-то делаем с объектом obj2
Что произойдет? Правильно. После выхода из блока и вызова деструктора для obj2 указатель в obj1 станет недействительным. Избежать этого можно переписав конструктор копий и оператор присваивания. Но есть и более простой путь – воспользоваться указателем с подсчетом ссылок.
Такой указатель можно реализовать самому. И наверняка многие так и делают. Но это не что иное, как изобретение велосипеда, название которому – boost::smart_ptr.
====== Как это использовать ======
===== scoped_ptr, shared_ptr, scoped_array, shared_array =====
Этот раздел библиотеки включает в себя группу классов, реализующих обе вышеописанные разновидности указателей:
**boost::scoped_ptr**
и
**boost::shared_ptr**
Первый используется для контроля времени жизни указателя в пределах операторного блока. Второй – для длительного контроля за указателем. Существуют разновидности этих классов для указателей на массивы:
**boost::scoped_array**
и
**boost::shared_array**
соответственно. Разница между этими группами классов очевидна. Первые для удаления контролируемого указателя используют оператор delete, а вторые – оператор delete[]. По этому путать их не рекомендуется во избежании возможных неприятностей с менеджером памяти.
В общем случае использовать экземпляры этих классов можно почти также, как и указатели:
boost::shared_ptr ptr(new SomeClass);
ptr->SomeFunc(); // вызываем метод класса по указателю
std::cout << *ptr; // выводим экземпляр класса в поток.
// а вот и то самое «почти»:
SomeClass* ptr1;
if (ptr == ptr1) // тут будет ошибка компиляции
// что-то делаем
Дело в том, что ведущим указателям не рекомендуется иметь оператора преобразования к указываемому типу (подробней об этом можно прочитать у Александреску). Для получения явного указателя на ведущий объект используется метод get.
Кроме метода **get** для этих классов определены следующие методы:
^ Метод ^ Назначение |
| **Общие** ||
^reset |Уменьшает счетчик ссылок на единицу (если для shared_ptr) и, если необходимо, удаляет объект |
^swap |Обменивает значения двух объектов |
| **scoped_array/shared_array** ||
^operator [] |Предоставляет доступ по индексу |
| **shared_ptr/shared_array** ||
^unique |Возвращает true, если счетчик ссылок == 1 |
^use_count |Возвращает значения счетчика ссылок |
^operator bool |Возвращает true, если указатель проинициализирован |
Подробно о сигнатурах этих методов можно прочитать в документации.
Приведенные выше примеры кода с использованием этих классов будут выглядеть так:
void SomeFunc()
{
scoped_array buff = new char[16];
_itoa(125, buff.get(), 10);
}
class SomeClass
{
public:
SomeClass() {m_SomeObject.reset(new int);}
private:
boost::shared_ptr m_SomeObject;
};
Применение этих классов достаточно прозрачно, и не должно представлять каких-либо затруднений.
Кроме этих четырех классов в джентельменский набор умных указателей входят еще два класса-помошника:
**boost::weak_ptr**
и
**boost::intrusive_ptr**
===== weak_ptr =====
Первый (boost::weak_ptr) используется в паре с shared_ptr для хранения ссылок на объекты типа boost::shared_ptr. Так же, как и обычные ссылки/указатели на shared_ptr, weak_ptr не увеличивает счетчик ссылок. Но, в отличии от обычных ссылок/указателей, weak_ptr предоставляет дополнительный сервис, заключающийся в том, что если хранимый в нем объект в какой-то момент времени удаляется «извне», то попытка получения его из weak_ptr приводит к исключению. Используется weak_ptr в случае, когда постоянное владение объектом не нужно, но, тем не менее, необходим прозрачный контроль над ним. Например:
std::vector > m_Vector;
std::map > m_Map;
Предполагается, что m_Vector хранит и владеет экземплярами класса SomeClass, а m_Map – предоставляет быстрый доступ к этим объектам по некоторой строке. Инициализироваться они будут так:
m_Vector.push_back(boost::shared_ptr(new SomeClass));
m_Map.insert(std::make_pair(m_Vector.back()->Key, m_Vector.back());
Теперь, если мы удалим какой-либо элемент из вектора, при попытке доступа к нему по ключу приведет к исключению. При этом дополнительных проверок на действительность ссылок делать не надо.
===== intrusive_ptr =====
Второй класс (boost::intrusive_ptr) используется при работе с объектами, использующими встроенный счетчик ссылок. Пример – COM-объекты. Этот класс предполагает, что определены функции intrusive_ptr_add_ref и intrusive_ptr_release (не являющиеся фунциями-членами хранимого класса), фактически увеличивающие или уменьшающие счетчик ссылок объекта. Для COM-объектов использование такого указателя могло бы выглядеть так:
int intrusive_ptr_add_ref(IUnknown* p) {return p->AddRef();}
int intrusive_ptr_release(IUnknown* p) {return p->Release();}
после чего можно смело писать:
boost::intrusive_ptr ptr(…);
Этот класс имеет интерфейс, аналогичный классу boost::shared_ptr, за исключением того, что его нельзя использовать в связке с boost::weak_ptr.
Функциональность описанных классов покрывает большинство задач, которые могут возникнуть при решении проблем с контролем указателей. Если же потребуется что-то специфическое (что мало вероятно) всегда можно изобрести свой собственный велосипед и использовать его.
Приведенное описание не претендует на полноту. Были опущены некоторые моменты, связанные с использованием классов, в частности класса weak_ptr. Для получения более полной информации можно заглянуть непосредственно в документацию: http://www.boost.org/libs/smart_ptr/smart_ptr.htm