智能指针
1.什么是智能指针
从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期。
在c++中,智能指针一共定义了4种:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。其中,auto_ptr 在 C++11已被摒弃,在C++17中已经移除不可用。
2.原始指针的问题
原始指针的问题大家都懂,就是如果忘记删除,或者删除的情况没有考虑清楚,容易造成悬挂指针(dangling pointer)或者说野指针(wild pointer)。
我们看个简单的例子
objtype *p = new objtype();
p -> func();
delete p;
上面的代码结构是我们经常看到的。里面的问题主要有以下两点:
1.代码的最后,忘记执行delete p的操作。
2.第一点其实还好,比较容易发现也比较容易解决。比较麻烦的是,如果func()中有异常,delete p语句执行不到,这就很难办。有的同学说可以在func中进行删除操作,理论上是可以这么做,实际操作起来,会非常麻烦也非常复杂。
此时,智能指针就可以方便我们控制指针对象的生命周期。在智能指针中,一个对象什么情况下被析构或被删除,是由指针本身决定的,并不需要用户进行手动管理,是不是瞬间觉得幸福感提升了一大截,有点幸福来得太突然的意思,终于不用我自己手动删除指针了。
3.unique_ptr
unique_ptr是独享被管理对象指针所有权(owership)的智能指针。unique_ptr对象封装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
创建unique_ptr:
void f1()
{
unique_ptr<int> p(new int(5));
cout<<*p<
}
或者
std::unique_ptr
需要注意的是,unique_ptr没有复制构造函数,不支持普通的拷贝和赋值操作。因为unique_ptr独享被管理对象指针所有权,当p2, p3失去p的所有权时会释放对应资源,此时会执行两次delete p的操作。
void f1() {unique_ptr<int> p(new int(5));
cout<<*p<
unique_ptr<int> p2(p);//非法
unique_ptr<int> p3 = p;//非法
}
对于p2,p3对应的行,IDE会提示报错:
无法引用 函数 "std::__1::unique_ptr<_Tp, _Dp>::unique_ptr(const std::__1::unique_ptr
unique_ptr虽然不支持普通的拷贝和赋值操作,但却可以将所有权进行转移,使用std::move方法即可。
void f1() {
unique_ptr
unique_ptr
cout<<*p2<
}
unique最常见的使用场景,就是替代原始指针,为动态申请的资源提供异常安全保证。
objtype *p = new objtype();p -> func();
delete p;
如果我们修改一下
unique_ptrp -> func();
delete p;
此时我们只要unique_ptr创建成功,unique_ptr对应的析构函数都能保证被调用,从而保证申请的动态资源能被释放掉。
4.shared_ptr
我们提到的智能指针,很大程度上就是指的shared_ptr,shared_ptr也在实际应用中广泛使用。它的原理是使用引用计数实现对同一块内存的多个引用。在最后一个引用被释放时,指向的内存才释放,这也是和 unique_ptr 最大的区别。当对象的所有权需要共享(share)时,share_ptr可以进行赋值拷贝。
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。
std::shared_ptr
上面这种写法是错误的,因为右边得到的是一个原始指针,前面我们讲过shared_ptr本质是一个对象,将一个指针赋值给一个对象是不行的。
shared_ptr<int> p = make_shared<int>(1);shared_ptr<int> p2(p);
shared_ptr<int> p3 = p;
以上写法都是可以的.
std::shared_ptr 可以通过get() 方法来获取原始指针,通过reset() 来减少一个引用计数,并通过use_count() 来查看一个对象的引用计数。例如:
auto pointer = std::make_sharedauto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << pointer << std::endl; //0x8879fc
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << pointer2 <
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已reset
5.shared_ptr使用需要注意的点
5.1 不能将一个原始指针初始化多个shared_ptr
void f2() {
int *p0 = new int(1);
shared_ptr
shared_ptr
cout<<*p1<
上面代码就会报错。原因也很简单,因为p1,p2都要进行析构删除,这样会造成原始指针p0被删除两次,自然要报错。
5.2.循环引用问题
shared_ptr最大的坑就是循环引用。引用网络上的一个例子:
struct Father
{
shared_ptr
};
struct Son
{
shared_ptr
};
int main()
{
auto father = make_shared
auto son = make_shared
father->son_ = son;
son->father_ = father;
return 0;
}
该部分代码会有内存泄漏问题。原因是
1.main 函数退出之前,Father 和 Son 对象的引用计数都是 2。
2.son 指针销毁,这时 Son 对象的引用计数是 1。
3.father 指针销毁,这时 Father 对象的引用计数是 1。
4.由于 Father 对象和 Son 对象的引用计数都是 1,这两个对象都不会被销毁,从而发生内存泄露。
为避免循环引用导致的内存泄露,就需要使用 weak_ptr。weak_ptr 并不拥有其指向的对象,也就是说,让 weak_ptr 指向 shared_ptr 所指向对象,对象的引用计数并不会增加。
使用 weak_ptr 就能解决前面提到的循环引用的问题,方法很简单,只要让 Son 或者 Father 包含的 shared_ptr 改成 weak_ptr 就可以了。
struct Father
{
shared_ptr
};
struct Son
{
weak_ptr
};
int main()
{
auto father = make_shared
auto son = make_shared
father->son_ = son;
son->father_ = father;
return 0;
}
1.main 函数退出前,Son 对象的引用计数是 2,而 Father 的引用计数是 1。
2.son 指针销毁,Son 对象的引用计数变成 1。
3.father 指针销毁,Father 对象的引用计数变成 0,导致 Father 对象析构,Father 对象的析构会导致它包含的 son_ 指针被销毁,这时 Son 对象的引用计数变成 0,所以 Son 对象也会被析构。
————————————————
原文链接:https://blog.csdn.net/bitcarmanlee/article/details/124847634 有修改.