广州自助网站搭建建站公司,福永镇网站建设,wordpress知更鸟more,网站301设置智能指针专题 1.普通指针的问题2.智能指针是什么什么是所有权 3.智能指针三个好处#xff1a;4.C11提供的智能指针4.1 shared_ptr#xff08;共享所有权指针#xff09;4.1.1 分配内存4.1.2 成员函数4.1.3 计数情况汇总#xff1a;4.1.4 示例代码(计数)4.1.5 示例代码(rese… 智能指针专题 1.普通指针的问题2.智能指针是什么什么是所有权 3.智能指针三个好处4.C11提供的智能指针4.1 shared_ptr共享所有权指针4.1.1 分配内存4.1.2 成员函数4.1.3 计数情况汇总4.1.4 示例代码(计数)4.1.5 示例代码(reset)4.1.6 自定义删除器4.1.7 shared_ptr的陷阱 4.2 unique_ptr(独占所有权指针)4.2.1 分配内存4.2.2 成员函数4.2.3 拷贝赋值4.2.4 release4.2.5 reset4.2.6 自定义删除器4.2.7 unique_ptr的陷阱不要与裸指针混用 5.性能6.选择指针6.1 unique_ptr场景示例:6.1.1 作为参数6.1.2 作为返回值 6.2 shared_ptr场景示例:6.2.1 作为参数 参考资料 1.普通指针的问题 1.访问失败如果一块内存被多个指针引用但其中的一个指针释放且其余的指针并不知道这样的情况下就发生了访问失败。 2.内存泄漏从堆中申请了内存后不释放回去就会引发内存泄漏。
2.智能指针是什么 在构造的时候分配内存当离开作用域的时候自动释放分配的内存这样的话开发人员就可以从手动动态管理内存的繁杂内容中解放出来。
每种智能指针都是以类模板的方式实现的shared_ptr 也不例外。
什么是所有权
https://blog.csdn.net/weixin_39722329/article/details/96301534 单一所有权所有权拥有着有义务去释放或转移所有权同一时刻只会有一个所有权拥有者 共享所有权和使用裸指针一样所有权的使用者不必释放内存引用计数器会负责释放内存同一时刻可以有多个所有权拥有者。
3.智能指针三个好处 1.明确资源的所属权 2.避免忘记delete这种比较容易犯错误 3.更好的处理异常
//函数结束后shared_ptr自动释放内存
void f(){shared_ptrint sp(new int(11));//假设抛出了异常而且在f中未捕获
}//函数结束后ip所指向的内存没有被释放。
void f1(){int* ip new int(12);//假设delete语句前抛出了异常而且在f中未捕获delete ip;
}重要性:123
4.C11提供的智能指针
4.1 shared_ptr共享所有权指针 共享指针shared_ptr是具有共享所有权语义的智能指针。 每当共享指针shared_ptr的最后一个所有者被销毁时关联对象都将被删除或关联资源被清除。
4.1.1 分配内存
std::make_shared
// make_sharedint分配一块int类型大小的内存并值初始化为100(返回值是shared_ptr类型因此可以直接赋值给sp)
shared_ptrint sp std::make_sharedint(100); new 接受指针参数的智能指针构造函数是explicit的直接初始化形式
// 错误 不会进行隐式转换类型不符合
shared_ptrint sp1 new int(100);
// 正确直接初始化调用构造函数
shared_ptrint sp2(new int(100000));4.1.2 成员函数
p.get() p.get()的返回值就相当于一个裸指针的值使用遵守以下几个约定 1.不要保存p.get()的返回值 2.无论是保存为裸指针还是shared_ptr都是错误的 3.保存为裸指针不知什么时候就会变成空悬指针 4.保存为shared_ptr则产生了独立指针 5.不要delete p.get()的返回值 6.会导致对一块内存delete两次的错误swap(p,q) 交换p、q中保存的指针shared_ptrT p(q) p是q的拷贝它们指向同一块内存互相关联p q 用q为p赋值之后p、q指向同一块内存q引用计数1p原来内存空间的引用计数-1p.use_count() 返回与p共享对象的智能指针数量shared_ptrT p(q,d) q是一个可以转换为T*的指针d是一个可调用对象作为删除器p接管q所指对象的所有权用删除器d代替delete释放内存p.reset() 将p重置为空指针p.reset(p) 将p重置为p的值p.reset(p,d) 将p重置为p的值并使用d作为删除器shared_ptr为什么没有release() 对于shared_ptr是可能存在多个shared_ptr指向同一块内存如果提供了release可能会造成错误的释放导致其他shared_ptr出现错误。
4.1.3 计数情况汇总
赋值(增加)
auto sp make_sharedint(1024); // sp的引用计数为1#include iostream
#include memoryusing namespace std;
// compile:g test.cpp -o a.exe -stdc11int main()
{{auto sp1 make_sharedstring(obj1);auto sp2(sp1);auto sp3 make_sharedstring(obj2);cout before sp2-use_count() sp2.use_count() \n;cout before sp3-use_count() sp3.use_count() \n;sp1 sp3; // 该操作会减少sp2的引用计数增加sp3的引用计数cout after sp2-use_count() sp2.use_count() \n;cout after sp3-use_count() sp3.use_count() \n;}return 0;
}该操作会减少sp2的引用计数增加sp3的引用计数。sp1、sp2指向对象obj1sp3指向对象obj2那么赋值之后sp1也会指向obj2那就是说指向obj1的就少了指向obj2的就会多。
拷贝(增加)
auto sp2 make_sharedint(1024);
auto sp1(sp2);该操作会使得sp1和sp2都指向同一个对象。
传参(拷贝)(增加) 而关于拷贝比较容易忽略的就是作为参数传入函数
auto sp2 make_sharedint(1024);
func(sp2); // func的执行会增加其引用计数reset(减少)
释放sp指向的对象( 而如果sp是唯一指向该对象的则该对象被销毁 )
sp.reset()4.1.4 示例代码(计数)
参考https://en.cppreference.com/w/cpp/memory/shared_ptr
#include iostream
#include memory// g test.cpp -o a.exe -stdc11 class Base
{
public:Base() { std::cout Base::Base()\n; }~Base() { std::cout Base::~Base()\n; }
};class Derived: public Base
{
public:Derived() { std::cout Derived::Derived()\n; }~Derived() { std::cout Derived::~Derived()\n; }
};void test(std::shared_ptrBase p)增加引用计数
{ std::cout local pointer in a function:\n p.get() p.get() , p.use_count() p.use_count() \n;std::shared_ptrBase lp p; std::cout local pointer in a function:\n lp.get() lp.get() , lp.use_count() lp.use_count() \n;
}//销毁ptr减少引用计数int main()
{std::shared_ptrBase p std::make_sharedDerived();std::cout func before:\n p.get() p.get() , p.use_count() p.use_count() \n;test(p);std::cout func after:\n p.get() p.get() , p.use_count() p.use_count() \n;
}运行结果
Base::Base()
Derived::Derived()
func before:p.get() 0x1028c30, p.use_count() 1
local pointer in a function:p.get() 0x1028c30, p.use_count() 2
local pointer in a function:lp.get() 0x1028c30, lp.use_count() 3
func after:p.get() 0x1028c30, p.use_count() 1
Derived::~Derived()
Base::~Base()4.1.5 示例代码(reset)
重置共享指针减少计数
#includememory
#includeiostream
// 编译 g test.cpp -o a.exe -stdc11using namespace std;
class A {
public:int i ;A() { cout construct\n; }~A() { cout delete i \n; }
};
int main()
{// 共享指针a,b,c都指向堆内存new A的位置shared_ptrA a( new A);shared_ptrA b(a);shared_ptrA c(b);shared_ptrA d(new A);a-i 10;cout a.use_count() endl;cout b.use_count() endl;d-i 30;// 错不要用p.get()的返回值为shared_ptr赋值,因为返回的是裸指针很容易被共享指针重复释放造成错误//a.reset(d.get())// 令a释放指向的空间 A//a.reset();// 令a释放指向的空间 A指向新空间a.reset(new A);a-i 20;cout b.use_count() endl;cout end endl;return 0;
}4.1.6 自定义删除器 如果用shared_ptr管理非new对象或是没有析构函数的类时应当为其传递合适的删除器原理是当删除器的指针Deleter传给shared_ptr/unique_ptr时shared_ptr/unique_ptr不会使用默认的delete val来释放其管理的资源而是使用Deleter(val)来释放资源这样就调用了Deleter来释放管理的资源。
1.普通删除函数定义类似于
void Deleter(T *val){// 其他代码// 释放val的内存delete val;// 或者(如果val是数组)delete[] val;
}#include iostream
#include memory
#include string
// 编译 g test.cpp -o a.exe -stdc11
using namespace std;class Connection{
public:string _name;explicit Connection(string name):_name(name){}string get_name() const {return _name;}
};void close(Connection* connection){cout string(关闭) connection-get_name() 管理的连接中... endl;// 关闭连接的代码// .....cout 关闭完成。 endl;
}// 函数式删除器
void Deleter(Connection *connection){close(connection);delete connection;
}int main(){// 新建管理连接Connection的智能指针shared_ptrConnection sp(new Connection(shared_ptr), Deleter);sp-_name hello;
}自定义释放规则用于申请的动态数组 对于申请的动态数组来说shared_ptr 指针默认的释放规则是不支持释放数组的只能自定义对应的释放规则才能正确地释放申请的堆内存。释放规则可以使用 C11 标准中提供的 default_delete 模板类我们也可以自定义释放规则
//1.指定 default_delete 作为释放规则
std::shared_ptrint p6(new int[10], std::default_deleteint[]());//2.自定义释放规则
void deleteInt(int*p) {delete []p;
}
//初始化智能指针并自定义释放规则
std::shared_ptrint p7(new int[10], deleteInt);//3.lambda方式构造和释放
std::shared_ptrint p7(new int[10], [](int* p) {delete[]p; }); 4.1.7 shared_ptr的陷阱
1.不要与裸指针混用 错误场景1
int *x(new int(10));
shared_ptrint sp1(x);
shared_ptrint sp2(x);//x随时可能变成空悬指针而无从知晓错误场景2
int *x(new int(10));
//创建了一个指向x指针所指内存的共享指针引用计数为1是引用这块内存的唯一共享指针func(shared_ptrint (x));
//离开函数即离开共享指针的作用域这块内存即被删除2.谨慎使用p.get()的返回值
shared_ptrint sp1(new int(10));
shared_ptrint sp2(sp1), sp3;
sp3 sp1;//一个典型的错误用法
shared_ptrint sp4(sp1.get());
cout sp1.use_count() sp2.use_count() sp3.use_count() sp4.use_count() endl;//输出:
3
3
3
1(独立)sp1,sp2,sp3是相互关联的共享指针共同控制所指内存的生存期sp4虽然指向同样的内存却是与sp1,sp2,sp3独立的sp4按自己的引用计数来关联内存的释放。
4.2 unique_ptr(独占所有权指针)
两个unique_ptr不能指向同一个对象不能进行复制操作只能进行移动操作。
4.2.1 分配内存
与shared_ptr不同unique_ptr没有定义类似make_shared的操作因此只可以使用new来分配内存并且由于unique_ptr不可拷贝和赋值初始化unique_ptr必须使用直接初始化的方式。
unique_ptrint up1(new int()); // okay:直接初始化std::unique_ptrint p4(new int);
std::unique_ptrint p5(std::move(p4)); // okay:调用移动构造函数,p5 将获取 p4 所指堆空间的所有权而 p4 将变成空指针nullptrunique_ptrint up2 new int(); // error! 避免隐式转换
unique_ptrint up3(up1); // error! 不允许拷贝4.2.2 成员函数 up.release() up放弃对它所指对象的控制权并返回保存的指针将up置为空不会释放内存 up.reset() 参数可以为空,内置指针先将up所指对象释放然后重置up的值
4.2.3 拷贝赋值 前面说了unique_ptr不可拷贝和赋值那要怎样传递unique_ptr参数和返回unique_ptr呢 事实上不能拷贝unique_ptr的规则有一个例外我们可以拷贝或赋值一个将要被销毁的unique_ptr
// 从函数返回一个unique_ptr
unique_ptrint func1(int a)
{return unique_ptrint (new int(a));
}// 返回一个局部对象的拷贝
unique_ptrint func2(int a)
{unique_ptrint up(new int(a));return up;
}或者是引用
void func1(unique_ptrint up){cout*upendl;
}unique_ptrint func2(unique_ptrint up){cout*upendl;return up;
}// 使用up作为参数
unique_ptrint up(new int(10));// 传引用不拷贝不涉及所有权的转移
func1(up);// 暂时转移所有权函数结束时返回拷贝重新收回所有权
up func2(unique_ptrint (up.release()));
// 如果不用up重新接受func2的返回值这块内存就泄漏了4.2.4 release
释放方法注意注意注意这里的释放并不会摧毁其指向的对象而且将其指向的对象释放出去。
#include iostream
#include memory//编译g test.cpp -o a.exe -stdc11int main () {std::unique_ptrint auto_pointer(new int);int * manual_pointer;*auto_pointer10;manual_pointer auto_pointer.release();// (auto_pointer is now empty)std::cout manual_pointer points to *manual_pointer \n;delete manual_pointer;return 0;
}执行结果为
manual_pointer points to 104.2.5 reset 重置方法销毁由该智能指针管理的任何可能存在的对象该智能指针被指为空
#include iostream
#include memory//编译g test.cpp -o a.exe -stdc11int main () {std::unique_ptrint up; // emptyup.reset (new int); // takes ownership of pointer*up5;std::cout *up \n;up.reset (new int); // deletes managed object, acquires new pointer*up10;std::cout *up \n;up.reset(); // deletes managed objectreturn 0;
}执行结果
5
104.2.6 自定义删除器
#include iostream
#include memory
#include string
// 编译 g test.cpp -o a.exe -stdc11
using namespace std;class Connection{
public:string _name;explicit Connection(string name):_name(name){}string get_name() const {return _name;}
};void close(Connection* connection){cout string(关闭) connection-get_name() 管理的连接中... endl;// 关闭连接的代码// .....cout 关闭完成。 endl;
}// 函数式删除器
void Deleter(Connection *connection){close(connection);delete connection;
}int main(){// 新建管理连接Connection的智能指针unique_ptrConnection, decltype(Deleter)* up(new Connection(unique_ptr), Deleter);up-_name hello;
}4.2.7 unique_ptr的陷阱
不要与裸指针混用
错误做法
int *x(new int());
unique_ptrint up1,up2;
// 会使up1 up2指向同一个内存
up1.reset(x);
up2.reset(x);unique_ptr不允许两个独占指针指向同一个对象在没有裸指针的情况下我们只能用release获取内存的地址同时放弃对对象的所有权这样就有效避免了多个独占指针同时指向一个对象。 正确做法
unique_ptrint up1(new int()); // okay,直接初始化
unique_ptrint up2;
up2.reset(up1.release());5.性能
内存占用高 shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外还要维护一个引用计数。 因此相比于 unique_ptr, shared_ptr 的内存占用更高原子操作性能低 考虑到线程安全问题引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。使用移动优化性能 shared_ptr 在性能上固然是低于 unique_ptr。而通常情况我们也可以尽量避免 shared_ptr 复制。 如果一个 shared_ptr 需要将所有权共享给另外一个新的 shared_ptr而我们确定在之后的代码中都不再使用这个 shared_ptr那么这是一个非常鲜明的移动语义。对于此种场景我们尽量使用 std::move将 shared_ptr 转移给新的对象。因为移动不用增加引用计数性能比复制更好。
6.选择指针
不是说任何地方都要使用智能指针,比如说你想传递一个对象到一个函数里那你就可以使用引用或者普通指针(raw ptr), 这里的引用和普通指针体现的是没有所属权ownership,也就是说函数本身不负责这个对象的生命周期。只有需要体现所属权ownership的创立和变动的时候采用智能指针。参考
选择条件 在使用智能指针的时候优先选用unique_ptr原因如下: 1.语义简单即当你不确定使用的指针是不是被分享所有权的时候默认选unique_ptr独占式所有权当确定要被分享的时候可以转换成shared_ptr 2.unique_ptr效率比shared_ptr高不需要维护引用计数和背后的控制块; 3.unique_ptr用起来更顺畅选择性更多可以转换成shared_ptr和通过get和release定制化智能指针custom smart pointer。
如果有多个指针指向同一对象的话你应该使用shared_ptr
如果一个对象只需要一个智能指针那你应该是用unique_ptr它非常适合于返回值类型为unique_ptr的函数
6.1 unique_ptr场景示例:
6.1.1 作为参数
因为不能被拷贝所以传递裸指针或者引用或者外部不需要了直接转移但是要注意不能传递值(拷贝)
// 裸指针
#includeiostream
#includememory
void test(int *p)
{*p 10;
}
int main()
{std::unique_ptrint up(new int(42));test(up.get());//传入裸指针作为参数std::cout*upstd::endl;//输出10return 0;
}// 引用
#includeiostream
#includememory
void test(std::unique_ptrint p)
{*p 10;
}
int main()
{std::unique_ptrint up(new int(42));test(up);std::cout*upstd::endl;//输出10return 0;
}// 转移
#includeiostream
#includememory
void test(std::unique_ptrint p)
{*p 10;
}
int main()
{std::unique_ptrint up(new int(42));test(std::unique_ptrint(up.release()));//test(std::move(up));//这种方式也可以return 0;
}6.1.2 作为返回值
返回可以用unique_ptr(伪代码),这里可以理解为返回值是拷贝了指向的地方相当于std::move获取唯一的所有权
unique_ptrint make_init(int n)
{return unique_ptrint (new int(n));
}int main()
{
···vectorunique_ptrint vp(size);for(int i 0; i vp.size(); i)vp[i] make_init(rand() % 1000)
···
}6.2 shared_ptr场景示例:
6.2.1 作为参数
#includeiostream
#includememory
void func0(std::shared_ptrint sp)
{std::coutfun0:sp.use_count()std::endl;
}void func1(std::shared_ptrint sp)
{std::coutfun1:sp.use_count()std::endl;
}void func2(std::shared_ptrint sp)
{std::coutfun1:sp.use_count()std::endl;
}int main()
{auto sp std::make_sharedint(1024);func0(sp); // 拷贝方式(这种方式unique不可以)func1(sp); // 拷贝方式(这种方式unique不可以)func2(sp); // 引用方式return 0;
}这里建议传参使用引用免拷贝。
参考资料
主要参考https://www.yanbinghu.com/categories/Cpp/
C11智能指针 https://www.jianshu.com/p/e4919f1c3a28
C11 shared_ptr智能指针http://c.biancheng.net/view/7898.html
shared_ptr官方讲解http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/
unique_ptr官方讲解http://www.cplusplus.com/reference/memory/unique_ptr/unique_ptr/
智能指针的选择https://blog.csdn.net/qq_22533607/article/details/82318595