一、智能指针的作用:
在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
动态内存管理经常会出现三种问题:
1、申请之后忘记释放内存,会造成内存泄漏;
2、另一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。
3、还有一种是内存的二次释放,即对同一个指针进行两次 free() 操作,可能导致程序崩溃
智能指针的作用就是解决上述三种可能出现的问题,指针指针的使用效率不会比一般的指针高,但是它胜在更安全、更稳定
二、智能指针的本质
智能指针的实质是一个类对象,它是利用模板类对一般的指针进行封装,在类内的构造函数实现对指针的初始化,并在析构函数里编写delete语句删除指针指向的内存空间。这样在程序过期的时候,对象会被删除,内存会被释放,实现指针的安全使用。
三、智能指针的类型和使用
智能指针是在C++11版本之后提供,包含在头文件#include<memory>中,智能指针有四种类型,分别是shared_ptr、unique_ptr、auto_ptr、weak_ptr,这里只介绍前两种
每种指针都有不同的使用范围,unique_ptr指针优于其它两种类型,除非对象需要共享时用shared_ptr。
如果你没有打算在多个线程之间来共享资源的话,那么就请使用unique_ptr。
1、shared_ptr
shared_ptr可以将多个指针指向相同的对象(共享)。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,对象的引用计数
加1,每析构一次,对象的引用计数减1,减为0时,自动删除所指向的堆内存。
shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
shared_ptr的初始化
智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一
个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的
shared_ptr的拷贝和赋值
拷贝使得对象的引用计数增加1,赋值使得原始对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
shared_ptr传参的过程就是内存的拷贝,使得对象的引用计数增加1
#include <iostream> #include <memory> using namespace std; shared_ptr<int> func(shared_ptr<int> ps) { (*ps)++; cout<<"ps.use_count()="<<ps.use_count()<<endl; return ps; } int main() { int n=100; //赋值 shared_ptr<int>p=make_shared<int>(n); //拷贝 shared_ptr<int>q (p); cout<<"q.use_count()="<<q.use_count()<<endl; cout<<"p.use_count()="<<p.use_count()<<endl; //传参 p=func(p); cout<<"p=.use_count()"<<p.use_count()<<endl; cout<<*p<<endl; }
get函数获取原始指针
我们前面说过,shared_ptr的本质是一个模板类,这里通过get()函数可以获取它的原始指针
//创建共享指针同时赋值 ClassA是一个类 shared_ptr<ClassA> b = make_shared<ClassA>(100); //b是一个类,c是类指针 ClassA* c=b.get()
注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
2、unique_ptr
unique_ptr"唯一"拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作
符,用户可指定其他操作)。
unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、
通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
#include <iostream> #include <memory> int main() { { int a = 10; //动态绑定对象 std::unique_ptr<int> p (new int(a)); std::cout<<*p<<std::endl; //move转移所有权,转移之后指针p就变得无效了 std::unique_ptr<int> q=std::move(p); std::cout<<*q<<std::endl; //release释放所有权,释放之后指针q变的无效 q.release(); } }
3、weak_ptr()
详细参考:https://blog.csdn.net/albertsh/article/details/82286999
四、智能指针的设计和实现
上面说过,智能指针的实质就是一个类对象,它是利用模板类对一般的指针进行封装,在类内的构造函数实现对指针的初始化,增加引用计数,并在析构函数里编写delete语句删除指针指向的内存空间。
1、智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;
2、拷贝构造函数:当对象作为另一对象的副本而创建时,拷贝指针并增加与之相应的引用计数;
3、对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(原始指针,如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
4、调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
智能指针就是模拟指针动作的类。所有的智能指针都会重载 =、-> 和 * 操作符。
一、shared_ptr()指针的实现
template < class T> class SmartPointer { private: //一般指针 T* _ptr; //计数 size_t* _count; public: //将普通指针ptr封装成智能指针,ptr为一个普通指针 _ptr(ptr)是构造函数形参列表的初始化赋值操作 SmartPointer(T* ptr = nullptr) : _ptr(ptr) { //如果被初始化的指针ptr不为空指针 if (_ptr) { _count = new size_t(1); } else { _count = new size_t(0); } } //指针指针的拷贝构造 SmartPointer(const SmartPointer& ptr) { if (this != &ptr) { this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; } } //赋值,重载=运算符 *this=ptr SmartPointer& operator=(const SmartPointer& ptr) { //判断是不是自己给自己赋值 if (this->_ptr == ptr._ptr) { return *this; } //等号左边的对象的 原始指针 计数减一 if (this->_ptr) { (*this->_count)--; if (this->_count == 0) { delete this->_ptr; delete this->_count; } } //复制之后,等号右边的对象引用计数加一,要注意的是_ptr和_count都是指针 this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; return *this; } //重载* T& operator*() { assert(this->_ptr == nullptr); return *(this->_ptr); } //重载-> T* operator->() { assert(this->_ptr == nullptr); return this->_ptr; } //析构函数 ~SmartPointer() { (*this->_count)--; if (*this->_count == 0) { delete this->_ptr; delete this->_count; } } //计数器 size_t use_count() { return *this->_count; } }; int main() { { //新建指针 SmartPointer<int> sp(new int(10)); //拷贝 SmartPointer<int> sp2(sp); //拷贝之后,sp2和sp的引用计数都加一 std::cout << sp2.use_count() << std::endl;//2 std::cout << sp.use_count() << std::endl;//2 //新建 SmartPointer<int> sp3(new int(20)); std::cout << sp2.use_count() << std::endl;//2 std::cout << sp3.use_count() << std::endl;//1 //赋值 sp2 = sp3; //赋值之后,sp3的引用计数加一,sp2的引用计数不变,但是sp2的原始指针的引用计数减一 std::cout << sp.use_count() << std::endl;//1 std::cout << sp2.use_count() << std::endl;//2 std::cout << sp3.use_count() << std::endl;//2 } system("pause"); return 0; }
二、unique_ptr()指针的实现
我们可以在类中把拷贝构造函数和拷贝赋值声明为private,这样就不可以对指针指向进行拷贝了,也就不能产生指向同一个对象的指针。
因为把拷贝构造函数和赋值操作符都声明为delete或private,这样每一个智能指针要指向一个对象时只能是指向一个新实例化的对象而不能通过“=”或者拷贝去指向前面已经创建了的对象,
例如“unique<A> ptr=&aa”,这里调用了赋值操作符这是不可以的
封装成unique_ptr()的类包括如下成员函数:
构造函数
析构函数
reset():释放源资源,指向新资源
release():返回资源,放弃对资源的管理
get():返回资源,只是供外部使用,依然管理资源
operator bool (): 是否持有资源
operator * ()
operator -> ()
拷贝构造函数,禁用,不支持
拷贝赋值函数,禁用,不支持
unique_ptr()指针是如何保证只有一个对象的引用?---------------------把拷贝构造函数和赋值操作符都声明为delete或private
#include<iostream> using namespace std; template<typename T> class UniquePtr { private: // 禁用拷贝构造 UniquePtr(const UniquePtr &) = delete; // 禁用拷贝赋值 UniquePtr& operator = (const UniquePtr &) = delete; //封装的普通指针 T *_ptr; //析构函数 void del() { if (nullptr == _ptr) return; delete _ptr; _ptr = nullptr; } public: //构造函数 UniquePtr(T *ptr = NULL) : _ptr(ptr){ } //析构 ~UniquePtr() { //释放内存私有化 del(); } // 改变指针指向,先释放资源(如果持有), 再持有资源 void reset(T* ptr) { del(); _ptr = ptr; } // 释放指针,把当前指针的所有权转移给调用方 T* release() { T* pTemp = _ptr; _ptr = nullptr; return pTemp; } // 获取资源,调用方应该只使用不释放,否则会两次delete资源 T* get(){ return _ptr; } // 是否持有资源 operator bool() const { return _ptr != nullptr; } T& operator * (){ return *_ptr; } T* operator -> (){ return _ptr; } }; int main() { { //创建指针并绑定对象 UniquePtr<int> sp(new int(10)); //创建空指针 UniquePtr<int> sp1; //绑定对象 sp1.reset(new int(11)); //输出指针内容 cout << *sp << " " << *sp1 << endl; //把sp指针的所有权转移给另一个普通指针,但是使用普通指针的时候不要释放, //否则会多次调用delete内存泄漏,慎用这个方法 int* pp = sp.get(); cout << *pp << " " << *sp << endl; //转移sp指针 int* p = sp.release(); cout << *p << endl;//sp为空指针 //新建 //UniquePtr<int> sp3(new int(20)); //禁用拷贝构造 //UniquePtr<int> sp2(sp); //禁用赋值 //sp = sp3; } system("pause"); return 0; }
参考博客:https://blog.csdn.net/runner668/article/details/80539221