引言
Understanding how deleters work in Boost’s shared_ptr, 2004. Boost’s reference-counting smart pointer shared_ptr has the interesting characteristic that you can pass it a function or function object during construction, and it will invoke this deleter on the pointed-to object when the reference count goes to zero.7 At first blush, this seems unremarkable, but look at the code:
template<typename T> class shared_ptr { public: template<typename U, typename D> explicit shared_ptr(U* ptr, D deleter); ... };
Notice that a shared_ptr<T> must somehow arrange for a deleter of type D to be invoked during destruction, yet the shared_ptr<T> has no idea what D is. The object can’t contain a data member of type D, nor can it point to an object of type D, because D isn’t known when the object’s data members are declared. So how can the shared_ptr object keep track of the deleter that will be passed in during construction and that must be used later when the T object is to be destroyed? More generally, how can a constructor communicate information of unknown type to the object it will be constructing, given that the object cannot contain anything referring to the type of the information?
The answer is simple: have the object contain a pointer to a base class of known type (Boost calls it sp_counted_base), have the constructor instantiate a template that derives from this base class using D as the instantiation argument (Boost uses the templates sp_counted_impl_p and sp_counted_impl_pd), and use a virtual function declared in the base class and defined in the derived class to invoke the deleter. (Boost uses dispose). Slightly simplified, it looks like this:
It’s obvious—once you’ve seen it. 8,9 But once you’ve seen it, you realize it can be used in all kinds of places, that it opens up new vistas for template design where templatized classes with relatively few template parameters (shared_ptr has only one) can reference unlimited amounts of information of types not known until later. Once I realized what was going on, I couldn’t help but smile and shake my head with admiration.10
来自 <http://www.artima.com/cppsource/top_cpp_aha_moments.html>
shared_ptr
在shared_ptr的实现中有一个地方个人觉得比较神奇:
先看shared_ptr的一般构造:
除了ptr成员指向保存的对象以外,ref_count也有一个ptr指向保存的对象,而shared_ptr在删除的时候其实是调用的delete ref_count.ptr,对于此的解释如下:
为什么图 1 中的 ref_count 也有指向 Foo 的指针?
shared_ptr<Foo> sp(new Foo) 在构造 sp 的时候捕获了 Foo 的析构行为。实际上 shared_ptr.ptr 和 ref_count.ptr 可以是不同的类型(只要它们之间存在隐式转换),这是 shared_ptr 的一大功能。分 3 点来说:
1. 无需虚析构;假设 Bar 是 Foo 的基类,但是 Bar 和 Foo 都没有虚析构。
shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*
shared_ptr<Bar> sp2 = sp1; // 可以赋值,自动向上转型(up-cast)
sp1.reset(); // 这时 Foo 对象的引用计数降为 1
此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为其 ref_count 记住了 Foo 的实际类型。
2. shared_ptr<void> 可以指向并安全地管理(析构或防止析构)任何对象;muduo::net::Channel class 的 tie() 函数就使用了这一特性,防止对象过早析构,见书 7.15.3 节。
shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*
shared_ptr<void> sp2 = sp1; // 可以赋值,Foo* 向 void* 自动转型
sp1.reset(); // 这时 Foo 对象的引用计数降为 1
此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,不会出现 delete void* 的情况,因为 delete 的是 ref_count.ptr,不是 sp2.ptr。
3. 多继承。假设 Bar 是 Foo 的多个基类之一,那么:
shared_ptr<Foo> sp1(new Foo);
shared_ptr<Bar> sp2 = sp1; // 这时 sp1.ptr 和 sp2.ptr 可能指向不同的地址,因为 Bar subobject 在 Foo object 中的 offset 可能不为0。
sp1.reset(); // 此时 Foo 对象的引用计数降为 1
但是 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为 delete 的不是 Bar*,而是原来的 Foo*。换句话说,sp2.ptr 和 ref_count.ptr 可能具有不同的值(当然它们的类型也不同)。
来自 <http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html>
所以为题的重点在于ref_count.ptr如何保存真正的类型,比如:
class foo
{
};
class bar : public foo
{
};
std::shared_ptr<foo> p(new bar());
我们知道shared_ptr的定义:
template<class _Ty>
class shared_ptr
{ … }
如何在_Ty已经指定为foo的时候,保存一个类型为bar的成员变量呢?
这里我们用到了模板成员函数的自动推导:
template<class _Ux>
explicit shared_ptr(_Ux *_Px)
{… }
在构造的时候,我们通过自动推导,来以Ux表示真正的类型,在上面,Ux被推导为bar,接下来的问题是如何来保存Ux这个类型呢?
答案是在shared_ptr里保存一个_Ref_count_base的类型,在最上面的图中,ref_count就是此类型的一个指针变量:
class _Ref_count_base
{ // common code for reference counting
private:
virtual void _Destroy() = 0;
virtual void _Delete_this() = 0;
};
// TEMPLATE CLASS _Ref_count
template<class _Ty>
class _Ref_count
: public _Ref_count_base
{ // handle reference counting for object without deleter
public:
_Ref_count(_Ty *_Px)
: _Ref_count_base(), _Ptr(_Px)
{ // construct
}
private:
virtual void _Destroy()
{ // destroy managed resource
delete _Ptr;
}
virtual void _Delete_this()
{ // destroy self
delete this;
}
_Ty * _Ptr;
};
_Ref_count_base是不需要模板参数的,因此在shared_ptr里可以轻松的声明,而在shared_ptr构造函数里,我们可以实现:
ref_count = new _Ref_count<_Ux>(_Px); //ref_count:_Ref_count_base
这样我们就实现了保存真正类型的功能,在析构时,通过虚函数的调用,在delete ref_count的时候会调用子类里的delete操作,从而删除的是保存的指针真正的类型。
Boost::any
同样,在boost::any的实现也是如此,如何使用any在无模板参数指定的情况下保存任何类型呢,下面的实现和shared_ptr思想一致:
05.//自定义的any类
06.class any
07.{
08.public:
09.
10. //保存真正数据的接口类
11. class placeholder
12. {
13. public:
14. virtual ~placeholder()
15. {
16. }
17. public:
18.
19. virtual const std::type_info & type() const = 0;
20. virtual placeholder * clone() const = 0;
21. };
22.
23. //真正保存和获取数据的类。
24. template<typename ValueType>
25. class holder : public placeholder
26. {
27. public:
28. holder(const ValueType & value): held(value)
29. {
30. }
31.
32. public:
33.
34. virtual const std::type_info & type() const
35. {
36. return typeid(ValueType);
37. }
38.
39. virtual placeholder * clone() const
40. {
41. return new holder(held);//使用了原型模式
42. }
43.
44. public:
45.
46. //真正的数据,就保存在这里
47. ValueType held;
48. };
49.
50.public:
51.
52. any(): content(NULL)
53. {
54. }
55.
56. //模板构造函数,参数可以是任意类型,真正的数据保存在content中
57. template<typename ValueType>
58. any(const ValueType & value): content(new holder<ValueType>(value))
59. {
60. }
61.
62. //拷贝构造函数
63. any(const any & other)
64. : content(other.content ? other.content->clone() : 0)
65. {
66. }
67.
68. //析构函数,删除保存数据的content对象
69. ~any()
70. {
71. if(NULL != content)
72. delete content;
73. }
74.
75.private:
76. //一个placeholde对象指针,指向其子类folder的一个实现
77. // 即content( new holder<ValueType>(value) )语句
78. placeholder* content;
79.
80. template<typename ValueType> friend ValueType any_cast(const any& operand);
81.public:
82.
83. //查询真实数据的类型。
84. const std::type_info & type() const
85. {
86. return content ? content->type() : typeid(void);
87. }
88.};