这段代码有两个奇妙之处:
1. A的析构函数是protected,编译时并没有错误提示
2. A的析构函数不是virtual的,但最后B的析构函数却被调用了
shared_ptr真是让人眼前一亮,那就到boost的里面来探一探究竟吧
先从shared_ptr的申明开始:
注意到这里有两个变量, px, pn,来看看它们是怎么被申明的吧:
px与pn都是用p来做初始化的,px的类型就是模块参数T的指针, 但pn的类型还要再看一看:
而这个pi的定义是:
sp_counted_base * pi_;
从sp_counted_base类中可以看出,这就是引用计数的具体实现,对我们这里的问题没有任何意义,不过pi实际指向的对象sp_counted_impl_p却有些意思:
其中的px_申明为:
X * px_;
好了,把上面这结连起来看,问题就很清楚了。
在创建shared_ptr<X>对象时,内部实际上初始化了两个指针,一个是shared_ptr里的px,其值就是原始指针p,另一个是pn,在shared_count中可以看出来,其类型是p的实际类型,也就是说,p的实际类型与shared_ptr的模板参数类型X可以不一样,当然也只能是派生关系,否则也编译不过去。
放到最开始的例子中就是,px类型为A, pn内部类型为B。
其实在shared_ptr的构造函数中有这样一段注释:“Y must be complete”,也就是说原始指针的类型必须是完整的,因为这时编译器可以检查出指针的真正类型,并且在pn中记录下该类型,然后在释放的时候就可以调用正确的析构函数了。
但是如果在这里指针的类型就已经丢失了,那结果自然是不正确的,比如我们可以这样试一下:
当然,直接这样编译是通不过的,编译器会提示你A的析构函数是protected,对象无法被删除。是的,这就是问题所在了,此时传递给shared_ptr的参数类型已经是A了,pn中记录的指针类型也是A,所以他在删除该指针对象的时候也就只会调用A的析构函数,这时自然是通不过编译检查的。
然后再来看一看boost为什么要这样做,一个指针保存了两份,当然是有他的原因的。仔细看一下会发现,在对指针进行操作时使用的是shared_ptr内保存的px对象,而在删除指针时用的是pn内保存的指针。这样就很明白了,用模板参数类型的指针,一般也就是基类指针来调用方法,可以很好的支持多态,而使用原始类型的指针来做删除操作,这样又能保证对象被安全的删除。
Boost果然非常神奇。