Студент увидел как-то на рынке, что какой-то мужик
продает мозги разных специалистов. Заинтересовался,
подходит, спрашивает что почем. Ему объясняют:
«Это мозги математика, они по сто рублей за килограмм,
это – мозги физика, они – по двести за кило, а вот эти
– мозги философа, они самые дорогие, по две тысячи».
Студент проникся уважением, спрашивает:
«Что, философские мозги и правда такие хорошие?»
«Да нет, дело не в этом, просто представь, это же
сколько философов отловить надо, чтобы набрать
целый килограмм мозгов!»
– мозги философа, они самые дорогие, по две тысячи».
Студент проникся уважением, спрашивает:
«Что, философские мозги и правда такие хорошие?»
«Да нет, дело не в этом, просто представь, это же
сколько философов отловить надо, чтобы набрать
целый килограмм мозгов!»
Прочитав Скотта Майерса, я стала противником auto_ptr. Auto_ptr - это зло-зло! Я об этом сказала своему начальнику. Он же сделал круглые глаза и выразил удивление, чем меня сильно озадачил. Я опять повторила: "Зло-Зло!". А он: "Почему?". Когда я объяснила, почему зло, он сделал простейший вывод: "Просто не надо их использовать в контейнерах." Действительно, подумала я... :)
И так небольшой обзор умных указателей:
STL:
- auto_ptr - только не для использования в контейнерах!! (См. Скотт Майерс, "Effective STL", совет №8)
- weak_ptr - (доступен только в ISO/IEC 14882:2011, или при указании опции компиляции -std=c++0x)
- shared_ptr - (доступен только в ISO/IEC 14882:2011, или при указании опции компиляции -std=c++0x)
И вообще, в файлы с новым стандартом лучше не заглядывать - незаметно сносит крышу. То есть вроде бы всё на месте, а холодком веет. Необычные конструкции вроде таких, пугают: shared_ptr(shared_ptr&& __r), но об этом можно почитать здесь.
Boost:
Во! Как бывает, так что читай - не читай документацию, всё равно надо смотреть в корень, как учит Козьма Прутков.
- scoped_ptr - самый простой умный указатель. Как написано в комментариях - simple solution for simple needs. Умеет возвращать память при выходе из области видимости (в деструкторе). Умеет делать get(), reset(), swap();
- intrusive_ptr - умный указатель со встроенным счетчиком ссылок. Может быть создан из произвольного raw-указателя (сырого указателя) типа T*. Блок памяти для intrusive_ptr такой же, как и для соответствующего raw-указателя. Умеет делать get(), reset(), swap(). Но для него придется определить функции подсчета ссылок и освобождения памяти. В конструкторе intrusive_ptr( T * p, bool add_ref = true ) вторым параметром (как это ясно из названия) можно повлиять на увеличение счетчика в первый раз. Его удобно использовать при разработке, где требуется самому определять не только, как освободить память, но и как работает счетчик ссылок. Главное правильно, если не ясно какой из двух использовать: его или shared_ptr, попробуйте использовать сначала shared_ptr;
- weak_ptr - слабенький указатель :). Используется совместно с shared_ptr. Хранит ссылку на объект, которым уже владеет shared_ptr. Для доступа к хранимому объекту нужно перейти к shared_ptr, используя соответствующий конструктор: template<class Y> explicit shared_ptr(weak_ptr<Y> const & r). Есть внутренние переменные: указатель на данные, счетчик ссылок (boost::detail::weak_count). Умеет делать lock() - защита от удаления объекта и сказать, что объект уже никем не используется с помощью функции expired();
- shared_ptr - умный указатель посложнее. Есть внутренние переменные - указатель на данные, счетчик ссылок (boost::detail::shared_count). Его использование гарантирует, что объект будет удален, когда последний shared_ptr, указывающий на него, уничтожится или сбросится (reset()).
shared_ptr в конструкторе можно передать функцию удаления для хранимого в shared_ptr объекта: shared_ptr(ptr, deleter).
Тогда при удалении будет вызван deleter(ptr):
#include <iostream>
#include <boost/shared_ptr.hpp>
struct A{
A(){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
~A(){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
struct myDeletor{
void operator()(const A* p){
std::cout << __PRETTY_FUNCTION__ << std::endl;
delete p;
}
};
void deletion(){
boost::shared_ptr<A> ptr(new A, myDeletor());
}
int main(){
deletion();
return 0;
}
Можно передать ещё и аллокатор. О как! Круто! Он, наверное, будет выделять память для нашего объекта, наивно подумала я. Но здравый смысл потряс меня за плечи и ткнул носом в new A. Стоп! А как это? Мы же уже выделили память для объекта:
boost::shared_ptr<A>(new A, myDeletor(), myAllocator())
.
Что же будет делать myAllocator? А, может быть, там извращение (которое сразу пришло мне в голову, слабонервным не читать). myDeletor() сразу удаляет new A, а myAllocator выделяет память заново?
Неееее! Там другое, myAllocator и не чешется выделять память под Ваш любимый объект, он ведет себя очень странно. Он выделяет память для счетчика ссылок, точнее для его имплементации (sp_counted_impl_pda). Опа! "Такой удар со стороны классика! А?". Смотрим в документацию. В документации написано, что конструктор:
выделяет память, используя копию a ("constructor allocates memory using a copy of a"), но тут и слова нет про то, для кого он выделяет память. "Я стоял перед этими гирями и безумно хохотал", вот как надо хитро писать, не придерешься. И нет никаких противоречий, а выделять память с помощью нашего аллокатора он будет для своих внутренних структур. myDeletor, как и в предыдущем варианте, вызовется при удалении A. А myAllocator для правильной работы должен удовлетворять стандартным требованиям для аллокаторов C++, поэтому у него должны быть определены и allocate() и deallocate() и ещё загадочная структура rebind:
template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
выделяет память, используя копию a ("constructor allocates memory using a copy of a"), но тут и слова нет про то, для кого он выделяет память. "Я стоял перед этими гирями и безумно хохотал", вот как надо хитро писать, не придерешься. И нет никаких противоречий, а выделять память с помощью нашего аллокатора он будет для своих внутренних структур. myDeletor, как и в предыдущем варианте, вызовется при удалении A. А myAllocator для правильной работы должен удовлетворять стандартным требованиям для аллокаторов C++, поэтому у него должны быть определены и allocate() и deallocate() и ещё загадочная структура rebind:
template<class T>
struct myAllocator{
myAllocator(){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template<class U>
myAllocator(const myAllocator<U>&){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
~myAllocator(){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template<class U> struct rebind {
typedef myAllocator<U> other;
};
T* allocate(size_t n, const void* = 0){
std::cout << __PRETTY_FUNCTION__ << std::endl;
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, size_t num){
std::cout << __PRETTY_FUNCTION__ << std::endl;
::operator delete(static_cast<void*>(p));
}
};
void deletion_allocation(){
boost::shared_ptr<A> ptr(new A, myDeletor(), myAllocator<A>());
// std::shared_ptr<A> ptr(new A, myDeletor(), myAllocator<A>());
}
int main(){
deletion_allocation();
return 0;
}
А вот, что происходит внутри (рассмотрим вариант, не бросающий исключения) /usr/include/boost/smart_ptr/detail/shared_count.hpp:145:
template<class P, class D, class A> shared_count( P p, D d, A a ): pi_( 0 )
{
typedef sp_counted_impl_pda<P, D, A> impl_type; // Вот она имплементация
typedef typename A::template rebind< impl_type >::other A2; // А это наш класс аллокатор,
// с шаблоном impl_type, то есть это myAllocator<impl_type>
A2 a2( a ); // Вот она копия 'a' - никто не наврал :)
try
{
// А вот и использование нашего аллокатора
pi_ = a2.allocate( 1, static_cast< impl_type* >( 0 ) );
new( static_cast< void* >( pi_ ) ) impl_type( p, d, a );
}
catch(...)
{
d( p ); // Вызываем myDeleter() для нашего объекта
if( pi_ != 0 )
{
// Если удалось выделить память для имплементации, освобождаем с помощью нашего аллокатора
a2.deallocate( static_cast< impl_type* >( pi_ ), 1 );
}
throw;
}
}
Во! Как бывает, так что читай - не читай документацию, всё равно надо смотреть в корень, как учит Козьма Прутков.
Всё это актуально и для std::shared_ptr, но он будет действовать только в новом стандарте.
P.S. Кстати, не забудем об умных массивах: shared_array, scoped_array.
P.S. Кстати, не забудем об умных массивах: shared_array, scoped_array.
Комментариев нет:
Отправить комментарий