最初,c++98首先引入了auto_ptr, 在接下来的c++11中又引入了unique_ptr, shared_ptr以及weak_ptr。
由于c++赋予了程序员直接操作内存的权限,使得c++程序的执行效率可以非常高。但是,直接操作内存也引入了很多潜在的问题,比如说内存泄漏或者野指针的问题。说到这两种问题,就有必要提一下c++的内存分配机制。c++内存大致可分为全局区(静态区),堆区和栈区,全局区中的变量在程序执行过程中一直存在,栈区的变量死亡后OS便会进行内存回收,堆区用来存储动态分配的内存,动态对象的内存由程序来控制,也就是说我们需要在代码中显式的销毁内存。
#include <iostream>
int data = 10; // 全局区
int main()
{
int a = 10; // 栈区
int* pre = new int(10); //堆区
return 0;
}
coder在代码中分配一块内存后,需要显式的释放,若过早释放会带来野指针的问题,若没有释放则会造成内存泄漏的问题。为了解决手动释放动态对象存在的潜在问题,智能指针被提出,它可以帮助coder在合适的位置释放内存,极大降低了程序中的内存问题。
auto_ptr为比较早提出的智能指针,本身这个类型有些缺陷,最好不要使用(大部分公司应该是强制不可使用),可以使用unique_ptr来代替auto_ptr。
auto_ptr<int> a1(new int(10));
auto_ptr<int> a2(new int(20));
a1 = a2;
printf("%d\n", *a2); // 错误,上一步操作将a2对资源的管理权给到了a1,此时再对a2访问会造成crush
相比auto_ptr,unique_ptr不在允许拷贝和赋值,防止出现上面提到的auto_ptr的问题(通过delete),下面代码大致写了一下unique_ptr的主要功能。
template <typename T>
class unique_ptr
{
public:
unique_ptr(T* p = nullptr) // 由此可见unique_ptr缺省值是nullptr,但是一个裸指针未初始化的
: ptr_(ptr) // 情况下不是nullptr,而是一个野指针!!!
{}
~unique_ptr()
{
if (ptr_ != nullptr)
{
delete ptr_;
ptr_ = nullptr; // 注意,如果只是delete指针的话并不会将指针置为nullptr
}
}
unique_ptr(unique_ptr<T>& up) = delete; // 禁止拷贝
unique_ptr& operator=(unique_ptr<T>& up) = delete; // 禁止赋值
// 以下两个函数在所有智能指针的类中都存在,重载运算符来使得智能指针可以和普通指针有相同的操作
逻辑
T& operator*()
{
return *ptr_;
}
T* operator->()
{
return ptr_;
}
private:
T* ptr_;
}
unique_ptr只允许资源被唯一管理,但有时候该指针可能需要被多个对象管理,此时引入了shared_ptr,相比unqiue_ptr,shared_ptr中多了一个计数器。每当shared_ptr被一个新的对象所管理,则计数器+1,当计数器个数为0时,则释放shared_ptr所管理的资源,模拟实现如下(和unqiue_ptr共有的内容不在展示)
template <typename T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr):
: ptr_(ptr)
, count_(new int(1))
{}
~shared_ptr()
{
if (--(*count_) == 0) { // 根据计数器来判断是否需要释放内存
if (ptr_ != nullptr)
{
delete ptr_;
ptr_ = nullptr;
}
delete count_;
count_ = nullptr;
}
}
shared_ptr(shared_ptr<T>& sp)
: ptr(sp.ptr_)
, count_(sp.count_)
{
++(*count_); // 计数器增加
}
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
if (ptr_ != sp.ptr_)
{
if (--(*count_) == 0) // 判断是否需要释放原来所管理的资源
{
delete count_;
count_ = nullptr;
}
ptr_ = sp.ptr_;
count_ = sp.count_;
++(*count_);
}
}
int use_count()
{
return *count_;
}
private:
T* ptr_;
int* count_;
}
weak_ptr是为了解决shared_ptr循环引用带来的问题,不是特别常用。本质上就是保存一个shared_ptr的裸指针,实现如下:
template <typename T>
class weak_ptr
{
public:
weak_ptr(const shared_ptr<T>& sp)
: ptr_(sp.get()) // 获取裸指针
{}
weak_ptr& operator=(cosnt shared_ptr<T>& sp)
{
ptr = sp.get();
return *this;
}
private:
T* ptr_;
}
1. 不要用智能指针的裸指针来新建智能指针
2. 使用智能指针的裸指针时要想清楚作用域
3. 有时候可以使用裸指针来提升代码执行效率,比如说明确知道在一个范围内智能指针不会释放,则在这个范围内可以使用该智能指针的裸指针进行传递。