权重7以上的网站,网站备案哪里管,深圳宝安seo,寻找锦州网站建设在C中没有垃圾回收机制#xff0c;必须自己释放分配的内存#xff0c;否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针#xff08;smart pointer#xff09;。智能指针是存储指向动态分配#xff08;堆#xff09;对象指针的类#xff0c;用于生存期的控…在C中没有垃圾回收机制必须自己释放分配的内存否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针smart pointer。智能指针是存储指向动态分配堆对象指针的类用于生存期的控制能够确保在离开指针所在作用域时自动地销毁动态分配的对象防止内存泄露。智能指针的核心实现技术是引用计数每使用它一次内部引用计数加1每析构一次内部的引用计数减1减为0时删除所指向的堆内存。
C11中提供了三种智能指针使用这些智能指针时需要引用头文件memory
1. std::shared_ptr共享的智能指针 2. std::unique_ptr独占的智能指针 3. std::weak_ptr弱引用的智能指针它不共享指针不能操作资源是用来监视shared_ptr的。
弱引用的智能指针是辅助共享的智能指针的。
共享的智能指针和独占的智能指针的区别 假设new出来一块int类型的内存使用共享的智能指针可以有多个共享的智能指针同时管理。
在每个智能指针的内部都有一个引用计数记录的当前的这块内存被多少个智能指针共同管理着。
当这个智能指针被析构或者重置的时候计数就会减一。注意在最后一个智能指针析构前会把管理的内存一并析构掉。
由上述这个例子不难看出共享智能指针可以多个指针同时管理那么独占智能指针就不允许多个指针同时管理一块内存。
若后来的这个独占智能指针想要管理这块内存就需要进行资源的转移也就是使用move函数实现资源的转移这样原来的独占智能指针就不能管理这块内存了。所以独占智能指针的计数永远为1。
1. shared_ptr的初始化
共享智能指针是指多个智能指针可以同时管理同一块有效的内存共享智能指针shared_ptr 是一个模板类如果要进行初始化有三种方式通过构造函数、std::make_shared辅助函数以及reset方法。共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count函数原型如下
// 管理当前对象的 shared_ptr 实例数量或若无被管理对象则为 0。
long use_count() const noexcept; 1.1 通过构造函数初始化
// shared_ptrT 类模板中提供了多种实用的构造函数, 语法格式如下:
std::shared_ptrT 智能指针名字(创建堆内存);
测试代码如下:
#include iostream
#include memory
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存shared_ptrint ptr1(new int(520));cout ptr1管理的内存引用计数: ptr1.use_count() endl;// 使用智能指针管理一块字符数组对应的堆内存shared_ptrchar ptr2(new char[12]);cout ptr2管理的内存引用计数: ptr2.use_count() endl;// 创建智能指针对象, 不管理任何内存shared_ptrint ptr3;cout ptr3管理的内存引用计数: ptr3.use_count() endl;// 创建智能指针对象, 初始化为空shared_ptrint ptr4(nullptr);cout ptr4管理的内存引用计数: ptr4.use_count() endl;return 0;
}测试代码输出的结果如下:
ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0
注意如果智能指针被初始化了一块有效内存那么这块内存的引用计数1如果智能指针没有被初始化或者被初始化为nullptr空指针引用计数不会1。另外不要使用一个原始指针初始化多个shared_ptr。
int *p new int;
shared_ptrint p1(p);
shared_ptrint p2(p); // error, 编译不会报错, 运行会出错
1.2 通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后就可以通过这个智能指针初始化其他新对象。在创建新对象的时候对应的拷贝构造函数或者移动构造函数就被自动调用了。
#include iostream
#include memory
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptrint ptr1(new int(520));cout ptr1管理的内存引用计数: ptr1.use_count() endl;//调用拷贝构造函数shared_ptrint ptr2(ptr1);cout ptr2管理的内存引用计数: ptr2.use_count() endl;shared_ptrint ptr3 ptr1;cout ptr3管理的内存引用计数: ptr3.use_count() endl;//调用移动构造函数shared_ptrint ptr4(std::move(ptr1));cout ptr4管理的内存引用计数: ptr4.use_count() endl;std::shared_ptrint ptr5 std::move(ptr2);cout ptr5管理的内存引用计数: ptr5.use_count() endl;return 0;
}测试程序输入的结果
ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 2
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 3
ptr5管理的内存引用计数: 3
通过拷贝构造函数的初始化
上述代码中的调用拷贝构造函数初始化首先用ptr1来初始化ptr2然后调用拷贝构造来初始化ptr3这时候ptr2和ptr3都指向相同的一块内存并且引用计数加一。 通过移动构造函数的初始化 移动构造就需要右值引用了右值引用就需要右值来进行初始化了。右值分为纯右值和将亡值这里使用的是将亡值。使用move函数就可得到将亡值了。上述代码中通过转移的方式将ptr2的资源转移给了ptr5引用计数并不能加一。
1.3 通过std::make_shared初始化 通过C提供的std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针函数原型如下
template class T, class... Args
shared_ptrT make_shared( Args... args );
1. T模板参数的数据类型 2. Args... args 要初始化的数据如果是通过make_shared创建对象需按照构造函数的参数列表指定
通过make_shared就能创建一块普通类型的内存也可以去创建某一个对象对应的内存。 测试代码如下:
#include iostream
#include string
#include memory
using namespace std;class Test
{
public:Test() {cout construct Test... endl;}Test(int x) {cout construct Test, x x endl;}Test(string str) {cout construct Test, str str endl;}~Test(){cout destruct Test ... endl;}
};int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptrint ptr1 make_sharedint(520);cout ptr1管理的内存引用计数: ptr1.use_count() endl;shared_ptrTest ptr2 make_sharedTest();cout ptr2管理的内存引用计数: ptr2.use_count() endl;shared_ptrTest ptr3 make_sharedTest(520);cout ptr3管理的内存引用计数: ptr3.use_count() endl;shared_ptrTest ptr4 make_sharedTest(我是要成为海贼王的男人!!!);cout ptr4管理的内存引用计数: ptr4.use_count() endl;return 0;
}使用std::make_shared()模板函数可以完成内存地址的创建并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型通过函数的可完成地址的初始化如果要创建一个类对象函数的内部需要指定构造对象需要的参数也就是类构造函数的参数。
1.4 通过 reset方法初始化
共享智能指针类提供的std::shared_ptr::reset方法函数原型如下
void reset() noexcept;template class Y
void reset( Y* ptr );template class Y, class Deleter
void reset( Y* ptr, Deleter d );template class Y, class Deleter, class Alloc
void reset( Y* ptr, Deleter d, Alloc alloc );1. ptr指向要取得所有权的对象的指针2. d指向要取得所有权的对象的指针3. aloc内部存储所用的分配器
测试代码如下
#include iostream
#include string
#include memory
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptrint ptr1 make_sharedint(520);shared_ptrint ptr2 ptr1;shared_ptrint ptr3 ptr1;shared_ptrint ptr4 ptr1;cout ptr1管理的内存引用计数: ptr1.use_count() endl;cout ptr2管理的内存引用计数: ptr2.use_count() endl;cout ptr3管理的内存引用计数: ptr3.use_count() endl;cout ptr4管理的内存引用计数: ptr4.use_count() endl;ptr4.reset();cout ptr1管理的内存引用计数: ptr1.use_count() endl;cout ptr2管理的内存引用计数: ptr2.use_count() endl;cout ptr3管理的内存引用计数: ptr3.use_count() endl;cout ptr4管理的内存引用计数: ptr4.use_count() endl;shared_ptrint ptr5;ptr5.reset(new int(250));cout ptr5管理的内存引用计数: ptr5.use_count() endl;return 0;
}测试代码输入的结果:
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0ptr5管理的内存引用计数: 1对于一个未初始化的共享智能指针可以通过reset方法来初始化当智能指针中有值的时候调用reset会使引用计数减1。 简单来说reset有两个功能
1. 使指向某块内存的智能指针解除对这块内存的管理。
2. 让这个指针管理另外一块内存相当于做了智能指针的重新初始化。
1.5 获取原始指针 通过智能指针可以管理一个普通变量或者对象的地址此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候就需要从智能指针对象中先取出数据的原始内存的地址再操作解决方案是调用共享智能指针类提供的get()方法其函数原型如下
T* get() const noexcept;
当一个智能指针管理一块内存的时候获取的原始指针是什么类型这个T*返回的就是什么类型。 也就是智能指针管理的这块内存的类型。
测试代码如下:
#include iostream
#include string
#include memory
using namespace std;int main()
{int len 128;shared_ptrchar ptr(new char[len]);// 得到指针的原始地址char* add ptr.get();memset(add, 0, len);strcpy(add, 我是要成为海贼王的男人!!!);cout string: add endl;shared_ptrint p(new int);*p 100;cout *p.get() *p endl;return 0;
}接下来通过一个示例程序来更加清楚的了解上述的前四个用法
代码如下
#includeiostream
#includememory
using namespace std;class Test
{
public:Test(){cout construct Test... endl;}Test(int x) : m_num(x){cout construct Testx x endl;}Test(string str){cout construct Teststr str endl;}~Test(){cout destruct Test... endl;}void setValue(int v){m_num v;}void print(){cout m_num m_num endl;}private:int m_num;
};int main()
{// 通过构造函数初始化shared_ptrint ptr1(new int(3));cout ptr1 use_count ptr1.use_count() endl;// 移动构造和拷贝构造函数初始化// 移动构造shared_ptrint ptr2 move(ptr1);cout ptr1 use_count ptr1.use_count() endl;cout ptr2 use_count ptr2.use_count() endl;// 拷贝构造// 需要一个实例对象来初始化所以要使用ptr2来初始化// 因为ptr1的资源已经转移到ptr2中了shared_ptrint ptr3 ptr2;cout ptr3 use_count ptr3.use_count() endl;cout ptr2 use_count ptr2.use_count() endl;// 通过std::make_shared初始化shared_ptrint ptr4 make_sharedint(8);// ()中的数就是给给new出来的这个int类型内存进行初始化shared_ptrTest ptr5 make_sharedTest(8);shared_ptrTest ptr6 make_sharedTest(luffy);// 通过reset初始化ptr6.reset();// 指针重置引用计数变为0cout ptr6 use_count ptr6.use_count() endl;ptr5.reset(new Test(ace));// 管理另一块内存cout ptr5 use_count ptr5.use_count() endl;// 获取原始指针return 0;
}输出结果为
ptr1 use_count1
ptr1 use_count0
ptr2 use_count1
ptr3 use_count2
ptr2 use_count2
construct Testx 8
construct Teststr luffy
destruct Test...
ptr6 use_count0
construct Teststr ace
destruct Test...
ptr5 use_count1
destruct Test...
对输出结果进行分析
当ptr1创建成功之后他的引用计数就是1。然后通过移动拷贝构造函数转移ptr1的资源到ptr2所以ptr1的引用计数变为0ptr2的引用计数变为1。紧接着ptr3进行的是一个拷贝构造所以他的引用计数和ptr2一样为2也就是说这块内存由ptr2和ptr3共同管理着。
ptr4和ptr5分别是整型和字符串类型然后就是重置ptr6ptr6管理的这个Test对象就被析构了所以输出析构语句引用计数也变为0。然后是让ptr5管理另一块内存输出构造函数中的语句所以ptr5之前管理的那块内存也析构了输出析构语句。又因为ptr5管理着新的内存所以引用计数仍然为1。最后程序结束ptr5管理的这块内存也析构了。 那么怎么去使用这个初始化得到的共享智能指针呢(有两种方式)
1. 通过智能指针取出原始地址
// 获取原始指针
Test* t ptr5.get();
t-setValue(1000);
t-print();
2. 通过智能指针对象直接进行操作
// 通过智能指针对象直接进行操作
// 将智能指针对象当作指针去使用
ptr5-setValue(999);
ptr5-print(); 2. 指定删除器 当智能指针管理的内存对应的引用计数变为0的时候这块内存就会被智能指针析构掉了。另外我们在初始化智能指针的时候也可以自己指定删除动作这个删除操作对应的函数被称之为删除器这个删除器函数本质是一个回调函数我们只需要进行实现其调用是由智能指针完成的。
// 第二个参数是指定删除器默认情况下可以不指定
// 删除器可以在外面创建一个或者写一个匿名函数(lambda表达式)
shared_ptrTest ppp(new Test(100), [](Test* t) {// 释放内存的操作 // 需要释放的是Test类型的指针// t指向的就是第一个参数cout ---------------------- endl;delete t;
});
输出结果为
----------------------
destruct Test...
destruct Test...
由此可以看出这个删除器确定被调用了。 那么在什么时候必须要指定删除器函数呢 通过智能指针对象去管理一块数组内存的时候如果不是数组内存智能指针对象提供的默认删除器函数就能删除掉这块内存。如果管理的是一块数组内存就不能删除。
接下来用一段代码来演示
#includeiostream
#includememory
using namespace std;class Test
{
public:Test(){cout construct Test... endl;}Test(int x) : m_num(x){cout construct Testx x endl;}Test(string str){cout construct Teststr str endl;}~Test(){cout destruct Test... endl;}void setValue(int v){m_num v;}void print(){cout m_num m_num endl;}private:int m_num;
};int main()
{shared_ptrTest p1(new Test[5]);return 0;
}输出结果为
construct Test...
construct Test...
construct Test...
construct Test...
construct Test...
destruct Test...
由输出结果可以看出默认的删除器函数不能删除数组内存。
手动指定删除器
shared_ptrTest p1(new Test[5], [](Test* t) {cout ---------------------- endl;delete[] t;
});
输出结果为
construct Test...
construct Test...
construct Test...
construct Test...
construct Test...
----------------------
destruct Test...
destruct Test...
destruct Test...
destruct Test...
destruct Test...
在删除数组内存时除了自己编写删除器也可以使用C提供的std::default_deleteT()函数作为删除器这个函数内部的删除功能也是通过调用delete来实现的要释放什么类型的内存就将模板类型T指定为什么类型即可。具体处理代码如下
int main()
{shared_ptrTest p2(new Test[5], default_deleteTest[]());return 0;
}
注意函数类型必须写[]不能写* 。而自己指定删除器中的参数类型可以也写成Test []t。 输出结果和自己指定一样。
如果我们在程序中需要经常对数组类型的内存进行释放我们可以去封装一个函数模板
代码如下
#include iostream
#include memory
using namespace std;template typename T
shared_ptrT make_share_array(size_t size)
{// 返回匿名对象return shared_ptrT(new T[size], default_deleteT[]());
}int main()
{shared_ptrint ptr1 make_share_arrayint(10);cout ptr1.use_count() endl;shared_ptrchar ptr2 make_share_arraychar(128);cout ptr2.use_count() endl;return 0;
}本文参考共享智能指针 | 爱编程的大丙 (subingwen.cn)