一,智能指针的概念
为什么要引入智能指针
C++语言需要自己手动释放申请的堆区资源,而程序中往往需要大量的使用new或malloc,如果一个忘记释放就会造成内存泄漏,而查找内存泄漏需要大量的精力。因此C++引入了智能指针来处理之一问题。
RAII技术
利用对象的生存期来控制资源 (内存,文件句柄等)的技术
在对象 构造时获取资源。
最后在对象 析构的时候释放资源。
借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
(1)不需要手动的去释放资源
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。
3. 智能指针的概念
所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
智能指针的基本框架:
智能指针类中必须有的内容:指针对象,构造函数,析构函数,operator*,operator->
template<class T>
class smart_ptr
{
private:
T* smartptr; //指针对象
public:
//构造
smart_ptr(T* ptr = nullptr):smartptr(ptr) {}
//operator*
T& operator*() const { return *smartptr; }
//operator->
T* operator->() const { return smartptr; }
//析构
~smart_ptr(){ delete smartptr; }
}
二,C++库中的智能指针
先写一个整形的包装类,下面仿写智能指针时会用到。
class Int
{
private:
int val;
public:
Int(int x = 0) :val(x) { cout << "构造Int" << endl; }
int& getvalue() { return val; }
void printvalue() { cout << val << endl; }
Int(const Int& a) :val(a.val)
{
cout << "拷贝构造Int" << endl;
}
Int& operator=(const Int& a)
{
if (this != &a)
{
val = a.val;
cout << "拷贝赋值Int" << endl;
}
return *this;
}
~Int() { cout << "析构Int" << endl; }
};
auto_ptr
介绍
auto_ptr是c98版本提出的智能指针(当时并没有右值引用与移动语义),它的采取的是管理权转移的思想,也就是一个auto_ptr对象拷贝给另一个auto_ptr对象时,原对象被置为nullptr,新对象获取资源
auto_ptr不能管理一组对象,只能管理单一对象。
auto_ptr的内置函数
template<class T>
//重新设置管理对象
void reset(T* other = nullptr)
//清除并返回管理对象
T* release()
auto_ptr的使用
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzA3M2Q3ZjljMjRkMTY4OTdjYjMyYWZkYzcwMTJlM2E1LnBuZw%3D%3D.png?w=700&webp=1)
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0L2ZiYTY0YzEzMzEyZTk5MzZjMzY2ZGY4YzgzNmJiNDhkLnBuZw%3D%3D.png?w=700&webp=1)
auto_ptr被舍弃的原因
当使用一个auto_ptr对象拷贝给另一个auto_ptr对象时,原对象被置为nullptr,新对象获取资源,此时对源对象进行操作将会导致系统崩溃。
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzgxZWEwNDdlYTNiMGVmMmFmYWZhNWEwMmEzOTc1NTZjLnBuZw%3D%3D.png?w=700&webp=1)
仿写auto_ptr
namespace ztw
{
//auto_ptr
template<class T>
class my_auto_ptr
{
private:
T* ptr;
public:
my_auto_ptr(T* p = nullptr) :ptr(p) {} //构造
T& operator*() const //重载*
{
return *ptr;
}
T* operator->() const //重载->
{
return ptr;
}
my_auto_ptr(my_auto_ptr& p) //拷贝构造
{
if (ptr != nullptr) { delete ptr; }
ptr = p.ptr;
p.ptr = nullptr;
}
my_auto_ptr& operator=(my_auto_ptr& p) //拷贝赋值
{
if (this == &p) { return *this; }
if (ptr != nullptr) { delete ptr; }
ptr = p.ptr;
p.ptr = nullptr;
return *this;
}
void reset(T* otherptr = nullptr) //重置管理对象
{
delete ptr;
ptr = otherptr;
}
T* release() //清处并返回管理对象
{
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
void swap(my_auto_ptr& other) //交换
{
std::swap(ptr, other.ptr);
}
~my_auto_ptr() { delete ptr; }
};
}
唯一性智能指针:unique_ptr
介绍
unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。但可以进行移动构造和移动赋值。
unique_ptr可以管理一组对象或一个对象。
unique_ptr的内置函数
//default_delete是默认删除器,删除单个元素
template<class T,class D = std::default_delete>
//获取unique_ptr指向的指针对象
T* get()
//获取unique_ptr指向的删除器
D& get_deleter()
//重置指针对象
void reset(T* other)
//清除并返回指针对象
T* release()
//交换指针对象
void swap(unique_ptr<T>& other)
unique_ptr的使用
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzIwNWU2NjQ1MDY3NTI1ZjM1ODI1MDA0ZjI0YTAxOGMwLnBuZw%3D%3D.png?w=700&webp=1)
仿写unique_ptr
仿写删除器
在删除器中重载()运算符,当调用 删除器对象()时,自动选择合适的删除器进行删除。
泛化版本删除器(删除单个对象)
template<class T>
struct my_default_deleter
{
void operator()(T* ptr) const
{
delete ptr;
}
}
特化版本删除器(删除一组对象)
template<class T>
struct my_default_deleter<T[]>
{
void operator()(T* ptr) const
{
delete []ptr;
}
}
仿写unique_ptr
泛化版本my_unique_ptr(管理单个对象资源)可以使用*和->
namespace ztw
{
class my_unique_ptr //泛化版本
{
private:
T* uniqueptr;
D mydeleter; //删除器对象
public:
my_unique_ptr(T* p = nullptr) :uniqueptr(p) {} //构造
T& operator*() const //重载*
{
return *uniqueptr;
}
T* operator->() const //重载->
{
return uniqueptr;
}
//删除拷贝构造和拷贝赋值
my_unique_ptr(const my_unique_ptr&) = delete;
my_unique_ptr& operator=(const my_unique_ptr&) = delete;
my_unique_ptr(my_unique_ptr&& ptr) //移动构造
{
if (uniqueptr != nullptr) { delete uniqueptr; }
uniqueptr = ptr.uniqueptr;
ptr.uniqueptr = nullptr;
}
my_unique_ptr& operator=(my_unique_ptr&& ptr) //移动赋值
{
if (this != &ptr)
{
if (uniqueptr != nullptr) { delete uniqueptr; }
uniqueptr = ptr.uniqueptr;
ptr.uniqueptr = nullptr;
}
return *this;
}
T* get_uniqueptr() const //获取指针对象
{
return uniqueptr;
}
D& get_deleter() const //获取删除器对象
{
return mydeleter;
}
void reset(T* other = nullptr)
{
if (uniqueptr != nullptr) { mydeleter(uniqueptr); }
uniqueptr = other;
}
T* release()
{
T* tmp = uniqueptr;
uniqueptr = nullptr;
return tmp;
}
void swap(my_unique_ptr& other)
{
std::swap(uniqueptr, other.uniqueptr);
std::swap(mydeleter, other.mydeleter);
}
operator bool() //重载bool()
{
return (uniqueptr != nullptr);
}
~my_unique_ptr() { reset(); }
};
}
特化版本my_unique_ptr(管理一组对象资源)不能使用*和->,但可以使用[]
namespace ztw //和泛化版本放在同一命名空间中,这里写只是为了方便看
{
template<class T, class D>
class my_unique_ptr<T[], D> //特化版本
{
private:
T* uniqueptr;
D mydeleter; //删除器对象
public:
my_unique_ptr(T* p = nullptr) :uniqueptr(p) {}
T& operator*() const = delete;
T* operator->() const = delete;
T* operator[](const std::size_t i)
{
//越界问题
return uniqueptr[i];
}
my_unique_ptr(const my_unique_ptr&) = delete;
my_unique_ptr& operator=(const my_unique_ptr&) = delete;
my_unique_ptr(my_unique_ptr&& ptr)
{
if (uniqueptr != nullptr) { delete uniqueptr; }
uniqueptr = ptr.uniqueptr;
ptr.uniqueptr = nullptr;
}
my_unique_ptr& operator=(my_unique_ptr&& ptr)
{
if (this != &ptr)
{
if (uniqueptr != nullptr) { delete uniqueptr; }
uniqueptr = ptr.uniqueptr;
ptr.uniqueptr = nullptr;
}
return *this;
}
T* get_uniqueptr() const
{
return uniqueptr;
}
D& get_deleter() const
{
return mydeleter;
}
void reset(T* other = nullptr)
{
if (uniqueptr != nullptr) { mydeleter(uniqueptr); }
uniqueptr = other;
}
T* release()
{
T* tmp = uniqueptr;
uniqueptr = nullptr;
return tmp;
}
void swap(my_unique_ptr& other)
{
std::swap(uniqueptr, other.uniqueptr);
std::swap(mydeleter, other.mydeleter);
}
operator bool()
{
return (uniqueptr != nullptr);
}
~my_unique_ptr() { reset(); }
};
}
删除器自动绑定原理
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0L2Q5ZDM4MDRlZDgzZjYxNGRkMjYxYmQwNzQ3ZWZiMTFmLnBuZw%3D%3D.png?w=700&webp=1)
共享智能指针:shared_ptr
介绍
share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,程序不会崩溃掉。
shared_ptr底层原理
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
shared_ptr有两个指针:_Ptr和_Rep
_Ptr:指向资源
_Rep:指向引用计数控制块
引用计数控制块有两个长整型属性:_Uses和_Weaks
_Uses:指向此资源共享指针的数目
_Weaks:指向此资源弱指针的数目
其中
当创建一个shared_ptr对象时,该对象的_Uses和_Weaks都置为1。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将_Uses减1。
如果_Uses减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,会先释放资源,再将_Weaks减1。
如果_Weaks减为0后,则表示该引用计数控制块没有人使用,会释放引用计数控制块
(使用make_shared创建智能指针则不是这样,make_shared创建的智能指针在_Uses减为0时,会将资源和引用计数控制块都释放,原理接下来会讲)
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzlkMDIxODkyYTY0NmFlZDRlYzA4MzQ2ZmYxMWM4OWJjLnBuZw%3D%3D.png?w=700&webp=1)
shared_ptr的内置函数
template<class T>
//获取unique_ptr指向的指针对象
T* get()
//重置指针对象
void reset(T* other)
//交换指针对象
void swap(unique_ptr<T>& other)
shared_ptr的使用
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzA0ZWI1ODliNGUxNWM3MDZlZDYxZDU4ZjBlYWE5OGJhLnBuZw%3D%3D.png?w=700&webp=1)
shared_ptr的循环引用问题
我们给出以下场景:
定义两个类,类中都有一个shared_ptr智能指针对象(指向另一个类)
class Child;
class Parent
{
public:
shared_ptr<Child> child;
public:
Parent() { cout << "构造Parent" << endl; }
void say()
{
cout << "Parent:child" << endl;
}
~Parent() { cout << "析构Parent" << endl; }
};
class Child
{
public:
shared_ptr<Parent> parent;
public:
Child(){ cout << "构造Child" << endl; }
void say()
{
cout << "Child:parent" << endl;
}
~Child() { cout << "析构Child" << endl; }
};
当我们分别用shared_ptr初始化这两个类对象,并用类中的shared_ptr指向另一个智能指针对象
int main()
{
shared_ptr<Child> pc(new Child());
shared_ptr<Parent> pp(new Parent());
pc->parent = pp;
pp->child = pc;
pc->say();
pp->say();
return 0;
}
此时就会出现问题:
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzRjOWQwMzRjN2M4ZDZiMTE2MGExNjI0ZGU5NWMyMmUwLnBuZw%3D%3D.png?w=700&webp=1)
产生这样问题的原因:
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0L2E5YWE3NWEyODY1NGQzZjkyMGRlNmViMmVjNzg4NDJkLnBuZw%3D%3D.png?w=700&webp=1)
为了解决shared_ptr的循环引用问题我们引入了弱指针weak_ptr
弱智能指针:weak_ptr
介绍
c++库中存在weak_ptr类型的智能指针。weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数。
weak_ptr的目的就是检测当前资源有多少shared_ptr使用
weak_ptr的底层原理
weak_ptr底层和shared_ptr一样,都是由两个指针_Ptr和_Rep构成
同时_Rep指向的引用计数控制器是所指向的shared_ptr的引用技术控制器。
只是weak_ptr不能直接操作资源。
当用shared_ptr构建weak_ptr对象时,会将引用计数控制块的_Weaks+1
weak_ptr的内置函数
template<class T>
//重置指针对象
void reset(T* other)
//返回当前指向此资源的shared_ptr的个数,也就是_Uses的值
unsigned long use_count()
//创建并返回一个shared_ptr对象
shared_ptr<T> lock()
//交换
swap(weak_ptr<T>& other)
weak_ptr的使用
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0LzVmYzA4MmZhODI2N2MzOWI4Y2I4NzBiNDg4Nzk0N2FlLnBuZw%3D%3D.png?w=700&webp=1)
使用weak_ptr解决shared_ptr的循环引用问题
我们将之前两个类中的shared_ptr改为weak_ptr
class Child;
class Parent
{
public:
weak_ptr<Child> child;
public:
Parent() { cout << "构造Parent" << endl; }
void say()
{
cout << "Parent:child" << endl;
}
~Parent() { cout << "析构Parent" << endl; }
};
class Child
{
public:
weak_ptr<Parent> parent;
public:
Child(){ cout << "构造Child" << endl; }
void say()
{
cout << "Child:parent" << endl;
}
~Child() { cout << "析构Child" << endl; }
};
int main()
{
shared_ptr<Child> pc(new Child());
shared_ptr<Parent> pp(new Parent());
pc->parent = pp;
pp->child = pc;
pc->say();
pp->say();
return 0;
}
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0L2EyMjFlMzNhNmM1YmUyOGNiN2UyMjNiZmM1YWVmODdlLnBuZw%3D%3D.png?w=700&webp=1)
此时可以看到完美的进行了析构。
原理:
![C++智能指针 C++智能指针](https://image.shishitao.com:8440/aHR0cHM6Ly9pbWctYmxvZy5jc2RuaW1nLmNuL2ltZ19jb252ZXJ0Lzk1ZWZkYTFiOWNhODE3NWFkZTUwNWU2NTkzNDhlOGRkLnBuZw%3D%3D.png?w=700&webp=1)
其实只改一个类中的shared_ptr为weak_ptr也能达到防止内存泄漏的效果,原理和上图分析方法一致。