目录
1、引言
2、unique_ptr
4、weak_ptr
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html C++11新特性很重要,作为C++开发人员很有必要去了解去学习,不仅笔试面试时会涉及到,开源代码中会大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例,WebRTC代码中大篇幅地使用了C++11及以上的新特性,要读懂其源码,必须要了解这些C++的新特性。所以,接下来一段时间我将结合工作实践,给大家详细讲解一下C++11的新特性,以供借鉴或参考。
1、引言
C++程序中使用的大部分内存都是动态申请来的堆内存,动态申请来的内存很容易出问题,要在正确的时间去释放内存没那么容易。有时我们如果忘记释放内存,就会产生内存泄漏(持续的内存泄漏会导致Out of memory内存耗尽);有时在尚有指针在引用内存的情况下我们就将内存释放了,就会产生引用非法内存的指针,就是我们常讲的野指针(或叫做悬空指针)。
为了更容易、更安全地使用动态申请的内存,C++中引入了智能指针的概念。其实早在1998年发布的C++98标准中就引入了智能指针auto_ptr,不过auto_ptr有一些缺陷,现在已经被废弃了。C++11中引入了unique_ptr、shared_ptr和weak_ptr三个智能指针,位于C++标准STL库中,在 <memory> 头文件中的 std 命名空间中定义的。
智能指针的大体机制和原理为:
C++智能指针用来存储指向动态分配(堆)对象指针的类,用于C++类对象的生存期的控制,确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每引用C++对象1次,内部引用计数就加1;智能指针每析构1次,内部的引用计数就减1,当引用计数减为0时,就会删除指向的C++类对象(释放类对象的堆内存)。
C++11标准中主要使用unique_ptr和shared_ptr两智能指针,weak_ptr则主要用来辅助shared_ptr,避免出现循环引用问题的。在使用unique_ptr和shared_ptr两个智能指针类时,可以使用指针运算符(-> 和 *)访问指向的对象,因为智能指针类重载了->和*运算符,以返回指向的对象(指针),相关代码如下:
_NODISCARD add_lvalue_reference_t<_Ty> operator*() const
{ // return reference to object
return (*get());
}
_NODISCARD pointer operator->() const noexcept
{ // return pointer to class object
return (this->_Myptr());
}
其中,get接口的实现如下:
_NODISCARD pointer get() const noexcept
{ // return pointer to object
return (this->_Myptr());
}
2、unique_ptr
unique_ptr是独占式智能指针,它拥有对其所指向对象的唯一所有权。unique_ptr 指针被销毁时,它所指向的对象也会被销毁。由于其具有独占性,所以unique_ptr不能被拷贝,只能被转移所有权。将对象的所有权转移到新的unique_ptr对象中,原先的unique_ptr对象不再指向原来的对象。
unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
使用unique_ptr的实例如下:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> up1(new int(11)); // 无法复制的unique_ptr
//unique_ptr<int> up2 = up1; // err, 不能通过编译
cout << *up1 << endl; // 11
unique_ptr<int> up3 = move(up1); // 现在p3是数据的唯一的unique_ptr
cout << *up3 << endl; // 11
//cout << *up1 << endl; // err, 运行时错误
up3.reset(); // 显式释放内存
up1.reset(); // 不会导致运行时错误
//cout << *up3 << endl; // err, 运行时错误
unique_ptr<int> up4(new int(22)); // 无法复制的unique_ptr
up4.reset(new int(44)); //"绑定"动态对象
cout << *up4 << endl;
up4 = nullptr;//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价
unique_ptr<int> up5(new int(55));
int *p = up5.release(); //只是释放控制权,不会释放内存
cout << *p << endl;
//cout << *up5 << endl; // err, 运行时错误
delete p; //释放堆区资源
return 0;
}
3、shared_ptr
shared_ptr是共享式智能指针,一个对象可以被多个shared_ptr共享。每个shared_ptr内部都维护一个引用计数,当引用计数为 0 时,所指向的对象会被销毁。std::shared_ptr 可以被拷贝和移动。
shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。
使用shared_ptr的实例如下:
int main()
{
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1;
cout << "count: " << sp2.use_count() << endl; //打印引用计数
cout << *sp1 << endl; // 22
cout << *sp2 << endl; // 22
sp1.reset(); //显式让引用计数减1
cout << "count: " << sp2.use_count() << endl; //打印引用计数
cout << *sp2 << endl; // 22
return 0;
}
4、weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它不具有普通指针的行为,没有重载*和->两个操作符,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源 (也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr 获得一个可用的shared_ptr对象,从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
使用weak_ptr的实例如下:
void check(weak_ptr<int> &wp)
{
shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
if (sp != nullptr)
{
cout << "still " << *sp << endl;
}
else
{
cout << "pointer is invalid" << endl;
}
}
int main()
{
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1;
weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
cout << "count: " << wp.use_count() << endl; //打印计数器
cout << *sp1 << endl; // 22
cout << *sp2 << endl; // 22
check(wp); // still 22
sp1.reset();
cout << "count: " << wp.use_count() << endl;
cout << *sp2 << endl; // 22
check(wp); // still 22
sp2.reset();
cout << "count: " << wp.use_count() << endl;
check(wp); // pointer is invalid
return 0;
}
5、shared_ptr循环引用问题(面试题)
使用shared_ptr可能会出现循环引用问题,场景是两个类中都包含了指向对方的shared_ptr对象,这样会导致new出来的两个类没有走析构,引发内存泄漏问题。
循环引用问题的示意图如下:
相关代码如下:
#include <iostream>
#include<memory>
using namespace std;
class B;
class A{
public:
shared_ptr<B> bptr;
~A(){cout<<"~A()"<<endl;}
}
class B
{
public:
shared_ptr<A> aptr;
~B( ){cout<<"~B()"<<endl;}
}
int main()
{
shared_ptr<A> pa(new A()); // 引用加1
shared_ptr<B> pb(new B()); // 引用加1
pa->bptr = pb; // 引用加1
pa->aptr = pa; // 引用加1
return 0;
}
执行到上述return 0这句代码时,指向A和B两个对象的引用计数都是2。当退出main函数时,先析构shared_ptr<B> pb对象,B对象的引用计数减1,B对象的引用计数还为1,所以不会delete B对象,不会进入B对象析构函数,所以B类中的shared_ptr<A> aptr成员不会析构,所以此时A对象的引用计数还是2。当析构shared_ptr<A> pa时,A的引用计数减1,A对象的引用计数变为1,所以不会析构A对象。所以上述代码会导致A和B两个new出的对象都没释放,导致内存泄漏。
为了解决上述问题,引入了weak_ptr,可以将类中包含的shared_ptr成员换成weak_ptr,如下:
相关代码如下:
#include <iostream>
#include<nemory>
using namespace std;
class B;
class A{
public:
weak_ptr<B> bptr; // 使用weak_ptr替代shared_ptr
~A(){cout<<"~A()"<<endl;}
}
class B
{
public:
weak_ptr<A> aptr; // 使用weak_ptr替代shared_ptr
~B( ){cout<<"~B()"<<endl;}
}
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->bptr = pb;
pa->aptr = pa;
return 0;
}