前面已经介绍了shared_ptr的基本使用,这篇文件主要介绍它的定制器和删除器的部分功能。
在一定程度上,删除器使用的概率会大很多。
在shared_ptr中有一个构造函数是这样的:
template<class Y, class D> shared_ptr(Y * p, D d): px(p), pn(p, d)
{
boost::detail::sp_enable_shared_from_this( this, p, p );
}
其中的D就是删除器,被传给了引用计数对象,然后引用计数对象在构造自己的时候,把删除器又传递给了具体的计数管理对象,最终在释放资源的时候使用。
对于删除器需要满足的要求是可以实现D(p)即可,可以是普通的函数(资源管理),可以是delete运算符,可以是函数对象,有一个参数是p都可以行。
举例代码如下:
(1)使用函数对象,实现文件资源句柄的关闭
class FileCloser
{
public:
void operator()(FILE* file)
{
std::cout << "The FileCloser has been called with a FILE*, "
"which will now be closed.\n";
if (file!=0)
fclose(file);
}
};
int main()
{
std::cout << "shared_ptr example with a custom deallocator.\n";
{
FILE* f=fopen("test.txt","r");
if (f==0)
{
std::cout << "Unable to open file\n";
throw "Unable to open file";
}
boost::shared_ptr<FILE> my_shared_file(f, FileCloser());
fseek(my_shared_file.get(),42,SEEK_SET);
}
std::cout << "By now, the FILE has been closed!\n";
return 0;
}
在自己工程的目录下建立一个test.txt文件即可运行。
结果如下:
可以看到函数对象正确的调用,并且进行了相关的资源处理,对于数据库连接资源等都可以这样进行管理。
其实上面也可以不用使用自定义删除器,直接使用相关的fclose函数也是可以的,这个时候就是使用函数指针,执行相关的文件关闭操作。
int main()
{
std::cout << "shared_ptr example with a custom deallocator.\n";
{
FILE* f=fopen("test.txt","r");
if (f==0)
{
std::cout << "Unable to open file\n";
throw "Unable to open file";
}
boost::shared_ptr<FILE> my_shared_file(f, &fclose);
fseek(my_shared_file.get(),42,SEEK_SET);
}
std::cout << "By now, the FILE has been closed!\n";
return 0;
}
传递一个函数指针进去即可。
关于分配器,因为在自定义上可能需要和stl的分配器模板适配,我自己学习的不是很好,故不在这里举例说明!
后期在补上。
补充知识:如何更安全的使用定制删除器。
编程的一个思想:当我们把基类的析构函数设定为protected的时候,delete操作符是不能直接作用于基类的指针,这样就可以保证在shared_ptr中利用基类类型保存派生内对象的时候,可以防止用户执行delete smaret.get()的操作。
那么如果想安全的使用删除器,我们也可以这么来做,声明析构函数为 protected (或 private) 并使用一个定制删除器来负责销毁对象。这个定制删除器必须是它要删除的类的友元,这样它才可以工作。封装这个删除器的好方法是把它实现为私有的嵌套类,如下例所示:
class A {
class deleter
{
public:
void operator()(A* p)
{
delete p;
}
};
friend class deleter;
public:
virtual void sing()
{
std::cout << "Lalalalalalalalalalala";
}
static boost::shared_ptr<A> createA()
{
boost::shared_ptr<A> p(new A(),A::deleter());
return p;
}
protected: virtual ~A() {};
};
int main()
{
boost::shared_ptr<A> p=A::createA();
delete p.get(); //会报编译时错误,无法使用析构函数
return 0;
}
上面就保证了,资源只能有自己的定制器删除,保证了安全性。
如何实现利用shared_ptr保存对象本身<实质就是保存this指针>
可能刚开始的初始实现如下:
class test
{
public:
shared_ptr<test> getThis()
{ return shared_ptr<this>(this);}
}
int main( void )
{
shared_ptr<test> a( new test());
shared_ptr<test> p = a->getThis();
return 0;
}
上面的实现感觉上是正确的,实质在运行的时候会出现错误,因为this指针给两个智能指针进行了管理,一个是a,一个是p,那么指针就会被删除两次,因为出现了利用“裸指针”初始化两个智能指针的错误。
那么如何解决这个问题了,在boost库中就提供了该机制,利用enable_shared_from_this
class A1 : public boost::enable_shared_from_this<A1>
{
public:
A1()
{
std::cout << "A1" << std::endl;
}
~A1() { std::cout << "~A1"<< std::endl; }
boost::shared_ptr<A1> get()
{
return shared_from_this();
}
};
int main()
{
boost::shared_ptr<A1> p( new A1 ); // this 1
boost::shared_ptr<A1> q = p->get(); // this 1
std::cout << p.use_count() << std::endl;
std::cout << q.use_count() << std::endl;
return 0;
}
实现的基本原理是:利用了weak_ptr保存了这个this指针,然后再通过weak_ptr构造一个新的shared_ptr,而不是用“裸指针”,因为weak_ptr不会影响引用计数,这样所有的智能指针就维护同一个指针对象。
构造的过程如下:
第一部:构建智能指针
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be complete
{
boost::detail::sp_enable_shared_from_this( this, p, p );
}
二:调用boost::detail::sp_enable_shared_from_this的重载函数
template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe )
{
if( pe != 0 )
{
pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
}
}
三、调用boost::enable_shared_from_this的_internal_accept_owner( ppx, const_cast< Y* >( py ) )
实现代码:
template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
{
if( weak_this_.expired() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
}
mutable weak_ptr<T> weak_this_;
这样就实现了weak_ptr指针的生成,并且保存了相关的this指针。
四:调用 shared_from_this()得到了共享指针,从weak_ptr,而不是“裸指针”
shared_ptr<T> shared_from_this()
{
shared_ptr<T> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}
总的来说:即使shared_ptr在构造自己的时候,就已经在适当的时候构建了相关的weak_ptr,然后再需要的时候返回了自己。所以这个方法是一个比较好的解决方法。
一定要记住:不能直接用this初始化shared_ptr哦,那样很容易出错了!