自己动手实现智能指针
上一讲实现的类
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{
//拷贝构造函数,等号右边或括号中的值是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 *