本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 在C、C++语言中,最经典的特性就是指针,他和内存相关,但是我们常常申请内存后忘记释放而导致内存泄漏,C++提供了智能指针去解决这个内存泄漏问题;
- C语言后面也会继续更新知识点,如内联汇编;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- 智能指针
- 简介
- 为什么要使用智能指针
- unique_ptr(类模板)
- 构造对象
- 删除器
- 指针使用
- 获取/释放
- get
- release
- reset
- shared_ptr
- 常见错误
- shared_ptr 内部指向两个位置
- 构造对象
- 删除器
- 指针使用
- 获取/释放
- use_count(是一个函数)
- unique
- get
- reset
- weak_ptr
- 简介
- 解决循环引用
- weak_ptr函数
- 我们需要使用enable_shared_from_this?
- 什么时候用enable_shared_ptr?
智能指针
简介
智能指针是一个模板类,封装了裸指针,可以对指针进行安全的操作。
- 使用RAII特点,将对象生命周期使用栈来管理
- 智能指针区分了所有权,因此使用责任更为清晰
- 智能指针大量使用操作符重载和函数内联特点,调用成本和裸指针无差别
为什么要使用智能指针
可以方便我们使用指针的时候,不用担心内存释放的问题。
unique_ptr(类模板)
👁 翻译:独有指针
☑️ 头文件:
🎪 特点:
- 两个
unique_ptr
不能指向一个对象,不能进行复制操作只能进行移动操作,📧 也就是说一个对象申请的内存如果用这个unique_ptr
智能指针去管理,只能用一个unique_ptr
去管理,不能用同时多个unique_ptr
管理。 - 不支持: 算术运算,不能通过
unique_ptr
操作指针的++、--; - 复制:只允许移动,不允许赋值;
- 作用域: 离开作用域自动释放内存。
构造对象
1、可以使用unique_ptr提供的构造函数构造对象,可以构造对象,也可以构造数组!
- 自己先申请一个内存,然后交接unique_ptr管理
std::unique_ptr<Test> p1(new Test);
std::unique_ptr<Test[]> p2(new Test[5]); //注意一下数组的申请
还可以这样:
auto p3 = std::make_unique<Test>();
auto p4 = std::make_unique<Test[]>(5); //注意Text[] 一定得声明数组
2、使用make_unique
构造,然后使用auto自动类型推导,就很方便了。
- make_unique 是将类型告诉他,它会自动申请一个内存。
unique_ptr<Test> up = p1; //尝试引用已删除的函数
unique_ptr<Test> up1 = std::move(p1); //可以移动
值得注意的是,unique_ptr
禁用了拷贝和赋值操作,只能进行移动。
删除器
在某些时候,默认的释放操作不能满足咱们的需要,这个时候就需要自定义删除器。(在构造智能指针的第二个参数指定他的释放类型)
// 可以用:全局函数、lambda、函数包装器、仿函数作为删除器,如下:
void del_global(Test* ptr){delete ptr;} //全局函数
auto del_lambda = [](Test* ptr) {delete ptr; }; //lambda
std::function<void(Test*)> del_function = del_lambda; //函数包装器
struct Del_Object //仿函数
{void operator()(Test* ptr){delete ptr;}
};int main()
{std::unique_ptr<Test, decltype(del_global)*> p2(new Test, del_global);// void(*)(Text*) typeid(Del_Object).name()std::unique_ptr<Test, decltype(del_lambda)> p3(new Test, del_lambda);std::unique_ptr<Test, decltype(del_function)> p4(new Test, del_function);std::unique_ptr<Test, decltype(Del_Object())> p5(new Test, Del_Object());//获取删除器 get_deleter方法auto delfun = p1.get_deleter();return 0;
}
指针使用
🏗 重载: 类中只用了大量的重载运算符,故和平常指针使用起来基本没差别。
判断
因为重载了operator bool()
函数,所以可以直接判断,指针是否为nullptr。
if(p1)
{}
if(!p1)
{}
解引用
因为重载了operator*()
函数,所以可以直接对指针解引用。
*p1;
访问成员
因为重载了operator->()
函数,所以可以直接获取对象的成员。
p1->~Test();
下标访问
因为重载了operator[]()
函数,如果管理的是数组,则可以通过下标访问元素。
p1[n];
获取/释放
get
使用get()
获取管理的对象原生指针,如果unique_ptr为空则返回nullptr。
void test()
{int* pp = nullptr;{auto p = std::make_unique<int>(2);std::cout << *p << std::endl;pp = p.get();}std::cout << *pp << std::endl; //输出垃圾值
}
release
使用release()
可以返回原生指针并释放所有权,智能指针管理的指针会变为nullptr。
🎫 注意: 这个时候就需要自己手动释放内存了。
void test()
{auto p = std::make_unique<int>(23);auto pp = p.release(); //智能指针p不在管理pp对象了 , 释放所有权,并且返回原生指针delete pp; //需要自己释放内存
}
reset
使用reset()
释放unique_ptr管理的对象,有一个可选参数,如果为nullptr,则只会释放对象,并指向nullptr。
注意:这个函数是释放unique_ptr
对象管理的指针,使其不在管理这个指针,这个时候如果传递了对象指针,则释放之后unique_ptr会接管传递的对象。
void test()
{unique_ptr<int> up;up.reset(new int(-22)); //获取指针的所有权cout << *up << endl;up.reset(new int(666)); //释放管理的内存,获取新的指针cout << *up << endl;up.reset(); //释放管理的内存
}
shared_ptr
unique_ptr
是一个独享指针,与同一时刻只能有一个unique_ptr
指向一个对象,而shared_ptr
是一个共享指针,同一时刻可以有多个shared_ptr
指向同一对象;
unique_ptr
会记录有多少个shared_ptr
共同指向一个对象,这便是所谓的引用计数;
🔽 **注意 注意 注意 **:use_count()
记录有多少个shared_ptr
共同指向一个对象,但是shared_ptr
不是通过原生指针赋值才使use_count + 1
的,是通过赋值shared_ptr
。
🎲 一旦某个对象的引用计数变为0,这个对象会被自动删除.
常见错误
- 用原生指针多次初始化
- 不明白 shared_ptr 中构造函数中 用了 explicit
- 用栈指针赋值
- 使用shared_ptr的get()初始化另一个shared_ptr
- 作为函数参数传递时,不要乱用引用,因为他本身有引用计数,乱用可能导致指针提前释放
shared_ptr 内部指向两个位置
- 指向对象的指针;
- 用于控制引用计数数据的指针。
构造对象
可以使用shared_ptr提供的构造函数构造对象,可以构造对象,也可以构造数组!
std::shared_ptr<Test> sp(new Test);
std::shared_ptr<Test[]> sp1(new Test[5]);
还可以这样:
auto sp2 = std::make_shared<Test>();
/值得会注意的是,使用 make_shared 不支持创建数组
//auto sp3 = std::make_shared<Test>(5);
使用make_shared
构造,然后使用auto自动类型推导,就很方便了,这也是本人常用的。
删除器
在某些时候,默认的释放操作不能满足咱们的需要,这个时候就需要自定义删除器。
std::shared_ptr<Test> sp(new Test, [](auto* ptr) {delete ptr; });
指针使用
和unique_ptr一样
获取/释放
use_count(是一个函数)
使用use_count
获取所指对象的引用计数。如果这是一个空的shared_ptr
,函数返回零。
unique
检查所管理对象是否仅由当前shared_ptr
管理。(判断shared_ptr
是不是独占的(只有本对象拥有资源的所有权))
get
同unique_ptr
;
reset
这个和unique_ptr
一样
void reset(); //仅释放,(如果有多个shared_ptr共享一个对象,那么就在引用-1,等到为0时,会自动释放资源
void reset(_Ux* _Px); //先释放,并接管_Px
void reset(_Ux* _Px, _Dx _Dt) //先释放,并接管_Px,并给_Px指定_Dt删除器
weak_ptr
简介
shared_ptr
可以用来避免内存泄漏,可以自动释放内存,但是在使用中可能存在循环引用,使引用计数失效,从而导致内存泄漏的情况。如下代码所示:
class Widget
{
public:Widget() { std::cout << __FUNCTION__ << std::endl; }~Widget() { std::cout << __FUNCTION__ << std::endl; }void setParent(std::shared_ptr<Widget>& parent) { _ptr = parent; }
private:std::shared_ptr<Widget> _ptr;
};int main()
{{std::shared_ptr<Widget> w1 = std::make_shared<Widget>();std::shared_ptr<Widget> w2 = std::make_shared<Widget>();std::cout << w1.use_count() << " " << w2.use_count() << std::endl;w1->setParent(w2);w2->setParent(w1);std::cout << w1.use_count() << " " << w2.use_count() << std::endl;}return 0;
}
输出结果:
Widget::Widget
Widget::Widget
1 1
2 2
我们发现,并没有调用析构函数,也就是对象没有释放,为什么会发生这样的事情呢?
- w1->_ptr 引用了w2,w2->_ptr 引用了w1,这样就形成了循环引用。
- 当w1超出作用域,释放的时候,发现管理的对象引用计数不为1,则不释放对象
- 当w2超出作用域,释放的时候,发现管理的对象引用计数也不为1,也不是放对象
- 这样就发生了内存泄漏!
weak_ptr
:
-
weak_ptr
: 的出现是为了解决,shared_ptr
的循环引用问题的,weak_ptr
是对shared_ptr
对象的一种弱引用,它不会增加对象的引用计数; -
循环引用:两个对象,每个对象都有一个
shared_ptr
成员,他们这个成员相互引用; -
weak_ptr
不会增加shared_ptr
的引用次数,weak_ptr
只能shared_ptr
构造; -
shared_ptr
可以直接赋值给week_ptr
,week_ptr
可通过调用lock函数来获得shared_ptr
。
解决循环引用
shared_ptr智能指针的循环引用导致的内存泄漏问题,可以通过weak_ptr解决。只需要将std::shared_ptr<Widget> _ptr
改为weak_ptr:
std::weak_ptr<Widget> _ptr;
输出结果为:
Widget::Widget
Widget::Widget
1 1
1 1
Widget::~Widget
Widget::~Widget
weak_ptr函数
- **use_count():**获取当前观察的资源的引用计数
- **expired():**判断所观察资源是否已经释放
- **lock():**返回一个指向共享对象的
shared_ptr
,如果对象被释放,则返回一个空shared_ptr:
使用场景:weak_ptr不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况。
这时就不能使用weak_ptr直接访问对象。必须用expired()
判断weak_ptr指向对象是否存在!
我们需要使用enable_shared_from_this?
- 需要在对象内部有一个指向当前对象的shared_ptr指针
- 当一个程序中,类的成员函数需要传递this指针给其他函数时候,如果构造另外一个shared_ptr传进去的化,回出错,因为这个相当于用另外一个智能指针去管理了,不是传递本身的智能指针
什么时候用enable_shared_ptr?
- 当我们需要在外部多次使用一个相同的
shared_ptr
时候。 - 当类的成员函数需要传递this指针给其他函数的时候(这个时候资源共享)
- 如下:
int main()
{//或直接构造智能指针↓auto obj = std::make_shared<SObject>();//获取指向obj的智能指针auto sp = obj->shared_from_this(); auto sp1 = obj->shared_from_this();std::cout << sp.use_count() << " " << sp1.use_count() << std::endl;if (!sp){std::cout << "is nullptr" << std::endl;}return 0;
}
输出:
1 1 // 共同使用一个shared_ptr
这个需要结合一些项目场景去理解,后面我们会更新aiso网络去构建Tcp服务器的文章,那里会有涉及到这个场景。