自己动手实现智能指针
上一讲实现的类
class shape_wrapper{
public:
explicit shape_wrapper(shape* ptr=nullptr):ptr_(ptr){}
~shape_wrapper(){delete ptr_;}
shape* get() const {return ptr_;}
private:
shape* ptr_;
};
该对象析构的时候,会对超出作用域的对象进行释放。
但是存在缺点:
- 这个类只适用于shape类
- 这个类的行为不够像指针
- 拷贝该类对象会引发程序行为异常
模板化和易用性
如果想让这个类能包含任意类型的指针,我们需要将它变为一个类模板,代码如下
template<typename T>
class smart_ptr{
public:
explicit smart_ptr(T* ptr=nullptr):ptr_(ptr){}
~smart_ptr(){delete ptr_};
T* get() const{return ptr_;}
private:
T* ptr_;
};
在开头添加模板声明template<typename T> ,然后将代码中的shape替换为T 就能完成定义。
模板的使用也很简单,把原来的shape_wrapper改为smart_ptr<shape> 就行。
目前这个类还是与指针类有差异的:
- 不能用
* 运算符解引用 - 不能用
-> 运算符指向对象成员 - 不能像指针一样用在布尔表达式里
所以增加几个成员函数就能解决:
template<typename T>
class smart_ptr{
public:
...
T& operator()const{return *ptr_;}
T* operator()const{return ptr_;}
operator bool()const{return ptr_;}
};
拷贝构造和赋值
最简单的情况是禁止拷贝。
template<typename T>
class smart_ptr{
...
smart_ptr(const smart_ptr&)=delete;
smart_ptr& operator=(const smart_ptr&)=delete;
...
};
禁用这两个函数会解决一种可能出错的情况,当smart_ptr<shape>ptr2{ptr1}; 在编译时不会出错,在运行时会有未定义行为–通常会对同一内存释放两次,通常情况下会导致程序崩溃。
是否可以考虑在拷贝智能指针时,将对象拷贝一份?不行,因为使用智能指针的目的就是为了减少对象的拷贝。而且我们的指针类型是shape,但实际指向的应该是circle或者triangle之类的对象。一般而言,没有通用的方法可以通过基类的指针构造出一个子类的对象。
尝试在拷贝时转移指针的所有权:
template<typename T>
class smart_ptr{
smart_ptr(smart_ptr& other){
ptr_=other.release();
}
smart_ptr& operator=(smart_ptr& rhs){
smart_ptr(rhs).swap(*this);
}
T* release(){
T* ptr =ptr_;
ptr_=nullptr;
return ptr;
}
void swap(smart_ptr& rhs){
using std::swap;
swap(ptr_,rhs.ptr_);
}
}
赋值分为拷贝构造和交换两步,异常只可能在第一步发生;而第一步如果发生异常的话,this对象完全不受任何影响。无论拷贝构造成功与否,结果只是赋值成功和赋值没有效果两种状态,而不会发生因为赋值破坏当前对象这种情况。
这个是auto_ptr的定义,但是已经被废除了,上面存在的最大问题在于会让程序员非常容易犯错,一不小心把他传递给另一个smart_ptr,你就不再拥有这个对象。
“移动”指针?
使用移动来改善行为,代码如下:
template <typename T>
class smart_ptr{
...
smart_ptr(smart_ptr&& other){
ptr_=other.release();
}
smart_ptr& operator=(smart_ptr rhs){
rhs.swap(*this);
return *this;
}
...
};
修改了两个地方:
- 把拷贝构造函数中的参数类型smart_ptr&改成了smart_ptr&&;现在它是移动构造函数。
- 把赋值函数中的参数smart_ptr&改为了smart_ptr,在构造参数时直接生成新的智能指针,从而不再需要从函数体中构造临时对象。现在赋值函数的行为是移动还是拷贝,完全依赖于构造参数时走的是移动构造还是拷贝构造。
根据C++的规则,如果我们提供了移动构造而没有手动提供拷贝构造函数,那么后者自动被禁用。于是:
smart_ptr<shape>ptr1{create_shape(shape_type::circle)};
smart_ptr<shape>ptr2{ptr1};
smart_ptr<shape>ptr3;
ptr3=ptr1;
ptr3=std::move(ptr1);
smart_ptr<shape> ptr4{std::move(ptr3)};
这个就是C++11的unique_ptr的基本行为。
子类类型向基类指针的转换
一个circle* 是可以隐式转换为shape* 的,但上面的smart_ptr<circle> 却无法自动转换成smart_ptr<shape> 。这个行为显然不够自然。
只需要额外添加一些模板代码,就能实现这一行为。
template <typename U>
smart_ptr(smart_ptr<U>&& other){
ptr_=other.release();
}
这样,利用指针的转换特性:现在smart_ptr<circle> 可以转移给smart_ptr<shape> 但是不能移动给smart_ptr<triangle> 。不正确的转换会在代码编译时直接报错。
疑问
那么,移动构造函数是在什么时候调用的,程序运行的时候circle类型的ptr1赋值给shape类型的ptr2并没有调用到移动构造,但是在转换ptr2为circle的时候调用了,然后给circle赋值时,再次调用到了移动构造
至于非隐式的转换,本来就是要写特殊的转换函数,留在本节的最后说。
引用计数
独占指针unique_ptr,一个对象只能被单个unique_ptr所拥有;
共享指针shared_ptr,多个智能指针同时拥有一个对象,当他们全部失效时,这个对象也同时会被删除。
多个不同的共享指针不仅可以共享同一个指针,而且可以共享一个对象,在共享一个对象的同时共享同一个计数。
实现如下:
class share_count{
public:
shared_count();
void add_count();
long reduce_count();
long get_count() const;
}
这个shared_count类除构造函数外有三个方法:一个增加计数,一个减少计数,一个获得计数。
增加计数的时候不需要返回计数值,但减少的时候必须返回计数值,因为需要判断是否是最后一个计数。
先实现一个非多线程安全的简单版本:
class shared_count{
public:
shared_count():count(1){}
void add_count(){
++count_;
}
long reduece_count(){
return --count_;
}
long get_count()const{
return count_;
}
private:
long count_;
}
代码:带有引用计数的智能指针
template <typename T>
class smart_ptr{
public:
explicit smart_ptr(T* ptr=nullptr):ptr_(ptr){
if(ptr){
shared_count_=new shared_count();
}
}
~smart_ptr(){
if(ptr_&&!shared_count_->reduce_count()){
delete ptr_;
delete shared_count_;
}
}
private:
T* ptr_;
shared_count* shared_count_;
};
构造函数跟之前的主要不同点是会构造一个shared_count出来,析构函数在看到ptr_非空时,需要对引用数-1,并且在引用数降为0时,彻底删除对象和共享计数。
为了方便实现赋值,需要一个新的swap函数
void swap(smart_ptr& rhs){
using std::swap;
swap(ptr-,rhs.ptr_);
swap(shared_count_,rhs.shared_count_);
}
赋值函数可以跟前面一样,保持不变,但拷贝构造和移动构造函数是需要更新一下的。
template <typename U>
smart_ptr(const smart_ptr<U>& other){
ptr_=other.ptr_;
if(ptr_){
other.shared_count_->add_count();
shared_count_=other.shared_count_;
}
}
template <typename U>
smart_ptr(smart_ptr<U>&& other){
ptr_=other.ptr_;
if(ptr_){
shared_count_=other.shared_count_;
other.ptr_=nullptr;
}
}
除复制指针以外,对于拷贝构造的情况,我们需要在指针非空的时候把引用计数加1,并复制共享计数的指针。
对于移动构造的情况,我们不需要调整引用数,直接把other.ptr_置为空,认为other不再指向该共享对象即可。
不过上面代码不能正确编译,因为模板的各个实例间并不是天然就有friend关系,因此不能互相访问私有成员ptr_ 和shared_count_ .需要在smart_ptr的定义中显式声明:
template <typename U>
friend class smart_ptr;
之前的实现用到了release来手工释放所有权。在目前的引用计数实现中,它就不太合适,应该删除。要加一个对调试非常有用的函数,返回引用计数。
long use_count() const{
if(ptr_){
return shared_count_->get_count();
}else{
return 0;
}
}
指针类型转换
对应C++里的不同类型强制转换:
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
智能指针需要实现类似的函数模板。实现本身并不复杂,但是为了实现这些转换,就需要添加构造函数,允许在对智能指针内部的指针对象赋值时,使用一个现有的智能指针的共享计数。
template<typename U>
smart_ptr(const smart_ptr<U>& other,T* ptr){
ptr_=ptr;
if(ptr_){
other.shared_count_->add_count();
shared_count_=other.shared_count_;
}
}
这样就可以实现转换所需的函数模板,下面实现一个dynamic_pointer_cast 来示例一下
template <typename T,typename U>
smart_ptr<T> dynamic_pointer_cast(const smart_ptr<U>& other){
T* ptr=dynamic_cast<T*>(other.get());
return smart_ptr<T>(other,ptr);
}
验证代码
smart_ptr<circle>ptr3=dynamic_pointer_cast<circle>(ptr2);
printf("use count of ptr3 is %ld\n",ptr3.use_count());
完整代码实现
#include <utility>
#include<iostream>
using namespace std;
class shared_count {
public:
shared_count() noexcept: count_(1) {}
void add_count() noexcept
{
++count_;
}
long reduce_count() noexcept
{
return --count_;
}
long get_count() const noexcept
{
return count_;
}
private:
long count_;
};
template < typename T>
class smart_ptr {
public:
template <typename U>
friend class smart_ptr;
explicit smart_ptr(T* ptr = nullptr): ptr_(ptr)
{
if (ptr) {
shared_count_ =new shared_count();
}
}
~smart_ptr()
{
printf("~smart_ptr(): %p\n", this);
if (ptr_ &&!shared_count_->reduce_count()) {
delete ptr_;
delete shared_count_;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
template <typename U>
smart_ptr(smart_ptr<U>&& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
shared_count_ =other.shared_count_;
other.ptr_ = nullptr;
}
cout << "移动构造" << endl;
}
template <typename U>
smart_ptr(const smart_ptr<U>& other,T* ptr) noexcept
{
ptr_ = ptr;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ =other.shared_count_;
}
cout << "+++++++++" << endl;
}
smart_ptr& operator=(smart_ptr rhs) noexcept
{
rhs.swap(*this);
return *this;
}
T* get() const noexcept
{
return ptr_;
}
long use_count() const noexcept
{
if (ptr_) {
return shared_count_->get_count();
}
else {
return 0;
}
}
void swap(smart_ptr& rhs) noexcept
{
using std::swap;
swap(ptr_, rhs.ptr_);
swap(shared_count_,rhs.shared_count_);
}
T& operator*() const noexcept
{
return *ptr_;
}
T* operator->() const noexcept
{
return ptr_;
}
operator bool() const noexcept
{
return ptr_;
}
private:
T* ptr_;
shared_count* shared_count_;
};
template <typename T>
void swap(smart_ptr<T>& lhs,smart_ptr<T>& rhs) noexcept {
lhs.swap(rhs);
}
template <typename T, typename U>
smart_ptr<T> static_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = static_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> reinterpret_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = reinterpret_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> const_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = const_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = dynamic_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
class shape {
public:
virtual ~shape() {}
};
class circle : public shape {
public:
~circle() { printf("~circle()"); }
};
int main()
{
smart_ptr<circle> ptr1(new circle());
printf("use count of ptr1 is %ld\n",ptr1.use_count());
smart_ptr<shape> ptr2(ptr1);
printf("use count of ptr2 was %ld\n",ptr2.use_count());
ptr2 = ptr1;
printf("use count of ptr2 is now %ld\n",ptr2.use_count());
if (ptr1) {
puts("ptr1 is not empty");
}
smart_ptr<circle> ptr3 = dynamic_pointer_cast<circle>(ptr2);
printf("use count of ptr3 is %ld\n", ptr3.use_count());
}
|