现代C++02:手动实现智能指针

本文探讨了如何使用C++模板创建一个智能指针类,以实现对象的自动管理。内容包括模板化的smart_ptr类,实现拷贝构造、赋值操作、类型转换以及引用计数。此外,还讨论了移动构造函数、类型安全转换以及在智能指针中应用C++11的unique_ptr行为。文章最后展示了如何通过引用计数实现多个智能指针共享对象的场景。
摘要由CSDN通过智能技术生成

自己动手实现智能指针

上一讲实现的类

class shape_wrapper{
public:
    explicit shape_wrapper(shape* ptr=nullptr):ptr_(ptr){}
    ~shape_wrapper(){delete ptr_;}
    shape* get() const {return ptr_;}
private:
    shape* ptr_;
};

该对象析构的时候,会对超出作用域的对象进行释放。

但是存在缺点:

  1. 这个类只适用于shape类
  2. 这个类的行为不够像指针
  3. 拷贝该类对象会引发程序行为异常

模板化和易用性

如果想让这个类能包含任意类型的指针,我们需要将它变为一个类模板,代码如下

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{
    //拷贝构造函数,等号右边或括号中的值是other
    //完成操作后,初始值被释放,新构造出的值指向初始值之前的地址
    smart_ptr(smart_ptr& other){
        ptr_=other.release();
    }
    //拷贝赋值函数,rhs是等号右边的值
    //通过拷贝构造一个临时对象,交换临时对象和当前对象的指针
    //结果就是等号两边的对象都完成了
    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};//error
smart_ptr<shape>ptr3;
ptr3=ptr1;//error
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> // std::swap
#include<iostream>
using namespace std;
/// <summary>
/// 引用计数类
/// </summary>
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_;
};
/// <summary>
/// 智能指针类
/// </summary>
/// <typeparam name="T"></typeparam>
template < typename T>
class smart_ptr {
public:
	template <typename U>
	friend class smart_ptr;
	/// <summary>
	/// 构造函数
	/// </summary>
	/// <param name="ptr"></param>
	explicit smart_ptr(T* ptr = nullptr): ptr_(ptr)
	{
		if (ptr) {
			shared_count_ =new shared_count();
		}
	}
	~smart_ptr()
	{
		printf("~smart_ptr(): %p\n", this);
		//如果指针为空并且引用计数为0,释放资源
		if (ptr_ &&!shared_count_->reduce_count()) {
			delete ptr_;
			delete shared_count_;
		}
	}
	/// <summary>
	/// 拷贝构造函数
	/// </summary>
	/// <typeparam name="U"></typeparam>
	/// <param name="other"></param>
	/// <returns></returns>
	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_;
		}
	}
	/// <summary>
	/// 移动构造函数
	/// </summary>
	/// <typeparam name="U"></typeparam>
	/// <param name="other"></param>
	/// <returns></returns>
	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;
	}
	/// <summary>
	/// 构造函数,用于类型转换
	/// </summary>
	/// <typeparam name="U"></typeparam>
	/// <param name="other"></param>
	/// <param name="ptr"></param>
	/// <returns></returns>
	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;
	}
	/// <summary>
	/// 拷贝赋值函数
	/// </summary>
	/// <param name="rhs"></param>
	/// <returns></returns>
	smart_ptr& operator=(smart_ptr rhs) noexcept
	{
		rhs.swap(*this);
		return *this;
	}
	/// <summary>
	/// 获取指针
	/// </summary>
	/// <returns></returns>
	T* get() const noexcept
	{
		return ptr_;
	}
	/// <summary>
	/// 获取引用计数
	/// </summary>
	/// <returns></returns>
	long use_count() const noexcept
	{
		if (ptr_) {
			return shared_count_->get_count();
		}
		else {
			return 0;
		}
	}
	/// <summary>
	/// 交换,类的成员函数,类和参数中的资源进行交换
	/// </summary>
	/// <param name="rhs"></param>
	/// <returns></returns>
	void swap(smart_ptr& rhs) noexcept
	{
		using std::swap;
		swap(ptr_, rhs.ptr_);
		swap(shared_count_,rhs.shared_count_);
	//	cout << "swap" << endl;
	}
	/// <summary>
	/// 解引用运算符
	/// </summary>
	/// <returns></returns>
	T& operator*() const noexcept
	{
		return *ptr_;
	}
	/// <summary>
	/// 箭头运算符
	/// </summary>
	/// <returns></returns>
	T* operator->() const noexcept
	{
		return ptr_;
	}
	/// <summary>
	/// bool运算符
	/// </summary>
	/// <returns></returns>
	operator bool() const noexcept
	{
		return ptr_;
	}
private:
	T* ptr_;
	shared_count* shared_count_;
};
/// <summary>
/// 交换
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
template <typename T>
void swap(smart_ptr<T>& lhs,smart_ptr<T>& rhs) noexcept	{
	lhs.swap(rhs);
	//cout << "swap" << endl;
}



/// <summary>
/// 转换函数 
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="other"></param>
/// <returns></returns>
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());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值