IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 现代C++02:手动实现智能指针 -> 正文阅读

[C++知识库]现代C++02:手动实现智能指针

作者:commentBox

自己动手实现智能指针

上一讲实现的类

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());
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-30 11:45:05  更:2021-09-30 11:47:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:00:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码