C++ 智能指针 - 全部用法详解
2021/8/2 14:06:34
本文主要是介绍C++ 智能指针 - 全部用法详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
为什么要学习智能指针?
咳咳,这个问题不是问大家的,是询问我自己的!
我依稀记得刚离校出来找实习工作那会,去面试一份工作,其中有一个环节需要答题;有一道题目就是问什么是智能指针?卧槽?当时我就懵逼,智能指针我压根就没有听说过…
最后,面试的这份工作理所应当的黄了。
差不多是一年前左右吧,现在趁有闲余时间,学习一下智能指针,丰富一下自己!
目录
- 一、为什么要使用智能指针
- 二、auto_ptr
- 三、unique_ptr
- auto_ptr 与 unique_ptr智能指针的内存管理陷阱
- 四、shared_ptr
- shared_ptr使用陷阱
- 五、weak_ptr
- 六、智能指针的使用陷阱
- 七、总结
一、为什么要使用智能指针
一句话带过:智能指针就是帮我们C++程序员管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏!
如下例子就是内存泄露的例子:
#include <iostream> #include <string> #include <memory> using namespace std; // 动态分配内存,没有释放就return void memoryLeak1() { string *str = new string("动态分配内存!"); return; } // 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了 int memoryLeak2() { string *str = new string("内存泄露!"); // ...此处省略一万行代码 // 发生某些异常,需要结束函数 if (1) { return -1; } delete str; // 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放 return 1; } int main(void) { memoryLeak1(); memoryLeak2(); return 0; }
memoryLeak1函数中,new了一个字符串指针,但是没有delete就已经return结束函数了,导致内存没有被释放,内存泄露!
memoryLeak2函数中,new了一个字符串指针,虽然在函数末尾有些释放内存的代码delete str,但是在delete之前就已经return了,所以内存也没有被释放,内存泄露!
使用指针,我们没有释放,就会造成内存泄露。但是我们使用普通对象却不会!
思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?
智能指针就是通过这个原理来解决指针自动释放的问题!
- C++98 提供了 auto_ptr 模板的解决方案
- C++11 增加unique_ptr、shared_ptr 和weak_ptr
二、auto_ptr
auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!
用法:
头文件: #include < memory >
用 法: auto_ptr<类型> 变量名(new 类型)
例 如:
auto_ptr< string > str(new string(“我要成为大牛~ 变得很牛逼!”));
auto_ptr<vector< int >> av(new vector< int >());
auto_ptr< int > array(new int[10]);
例:
我们先定义一个类,类的构造函数和析构函数都输出一个字符串用作提示!
定义一个私有成员变量,赋值20.
再定义一个私有成员方法用于返回这个私有成员变量。
class Test { public: Test() { cout << "Test的构造函数..." << endl; } ~Test() { cout << "Test的析构函数..." << endl; } int getDebug() { return this->debug; } private: int debug = 20; };
当我们直接new这个类的对象,却没有释放时。。。
int main(void) { Test *test = new Test; return 0; }
可以看到,只是打印了构造函数这个字符串,而析构函数的字符却没有被打印,说明并没有调用析构函数!这就导致了内存泄露!
解决内存泄露的办法,要么手动delete,要么使用智能指针!
使用智能指针:
// 定义智能指针 auto_ptr<Test> test(new Test);
智能指针可以像普通指针那样使用:
cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl;
这时再试试:
int main(void) { //Test *test = new Test; auto_ptr<Test> test(new Test); cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl; return 0; }
自动调用了析构函数。
为什么智能指针可以像普通指针那样使用???
因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。
具体原因不用深究,只需知道他为什么可以这样操作就像!
函数中返回的是调用get()方法返回的值,那么这个get()是什么呢?
智能指针的三个常用函数:
-
get() 获取智能指针托管的指针地址
// 定义智能指针 auto_ptr<Test> test(new Test); Test *tmp = test.get(); // 获取指针返回 cout << "tmp->debug:" << tmp->getDebug() << endl;
但我们一般不会这样使用,因为都可以直接使用智能指针去操作,除非有一些特殊情况。
函数原型:_NODISCARD _Ty * get() const noexcept { // return wrapped pointer return (_Myptr); }
-
release() 取消智能指针对动态内存的托管
// 定义智能指针 auto_ptr<Test> test(new Test); Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管 delete tmp2; // 之前分配的内存需要自己手动释放
也就是智能指针不再对该指针进行管理,改由管理员进行管理!
函数原型:_Ty * release() noexcept { // return wrapped pointer and give up ownership _Ty * _Tmp = _Myptr; _Myptr = nullptr; return (_Tmp); }
-
reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
// 定义智能指针 auto_ptr<Test> test(new Test); test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。
函数原型:void reset(_Ty * _Ptr = nullptr) { // destroy designated object and store new pointer if (_Ptr != _Myptr) delete _Myptr; _Myptr = _Ptr; }
使用建议:
-
尽可能不要将auto_ptr 变量定义为全局变量或指针;
// 没有意义,全局变量也是一样 auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
-
除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针;
auto_ptr<Test> t1(new Test); auto_ptr<Test> t2(new Test); t1 = t2; // 不要这样操作...
-
C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!C++11后不建议使用auto_ptr。
-
auto_ptr 被C++11抛弃的主要原因
1). 复制或者赋值都会改变资源的所有权
// auto_ptr 被C++11抛弃的主要原因 auto_ptr<string> p1(new string("I'm Li Ming!")); auto_ptr<string> p2(new string("I'm age 22.")); cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl; // p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针, // 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。 p1 = p2; cout << "p1 = p2 赋值后:" << endl; cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl;
2). 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
vector<auto_ptr<string>> vec; auto_ptr<string> p3(new string("I'm P3")); auto_ptr<string> p4(new string("I'm P4")); // 必须使用std::move修饰成右值,才可以进行插入容器中 vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 风险来了: vec[0] = vec[1]; // 如果进行赋值,问题又回到了上面一个问题中。 cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl;
访问越界了!
3). 不支持对象数组的内存管理
auto_ptr<int[]> array(new int[5]); // 不能这样定义
所以,C++11用更严谨的unique_ptr 取代了auto_ptr!
测试代码:
#include <iostream> #include <string> #include <memory> #include <vector> using namespace std; class Test { public: Test() { cout << "Test的构造函数..." << endl; } ~Test() { cout << "Test的析构函数..." << endl; } int getDebug() { return this->debug; } private: int debug = 20; }; // 不要定义为全局变量,没有意义 //auto_ptr<Test> test(new Test); void memoryLeak1() { //Test *test = new Test; // 定义智能指针 auto_ptr<Test> test(new Test); cout << "test->debug:" << test->getDebug() << endl; cout << "(*test).debug:" << (*test).getDebug() << endl; // get方法 Test *tmp = test.get(); // 获取指针返回 cout << "tmp->debug:" << tmp->getDebug() << endl; // release方法 Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管 delete tmp2; // 之前分配的内存需要自己手动释放 // reset方法:重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉 test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之 // 忠告:不要将智能指针定义为指针 //auto_ptr<Test> *tp = new auto_ptr<Test>(new Test); // 忠告:不要定义指向智能指针对象的指针变量 //auto_ptr<Test> t1(new Test); //auto_ptr<Test> t2(new Test); //t1 = t2; return; } int memoryLeak2() { //Test *test = new Test(); // 定义智能指针 auto_ptr<Test> test(new Test); // ...此处省略一万行代码 // 发生某些异常,需要结束函数 if (1) { return -1; } //delete test; return 1; } int main1(void) { //memoryLeak1(); //memoryLeak2(); //Test *test = new Test; //auto_ptr<Test> test(new Test); //cout << "test->debug:" << test->getDebug() << endl; //cout << "(*test).debug:" << (*test).getDebug() << endl; auto_ptr 被C++11抛弃的主要原因 //auto_ptr<string> p1(new string("I'm Li Ming!")); //auto_ptr<string> p2(new string("I'm age 22.")); // //cout << "p1:" << p1.get() << endl; //cout << "p2:" << p2.get() << endl; //p1 = p2; //cout << "p1 = p2 赋值后:" << endl; //cout << "p1:" << p1.get() << endl; //cout << "p2:" << p2.get() << endl; // 弊端2.在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制 vector<auto_ptr<string>> vec; auto_ptr<string> p3(new string("I'm P3")); auto_ptr<string> p4(new string("I'm P4")); vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 风险来了: vec[0] = vec[1]; cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 弊端3.不支持对象数组的内存管理 //auto_ptr<int[]> array(new int[5]); // 不能这样定义 return 0; }
三、unique_ptr
auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr 主要有三大问题:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
- 不支持对象数组的操作
以上问题已经在上面体现出来了,下面将使用unique_ptr解决这些问题。
所以,C++11用更严谨的unique_ptr 取代了auto_ptr!
unique_ptr 和 auto_ptr用法几乎一样,除了一些特殊。
unique_ptr特性
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
- 在容器中保存指针是安全的
A. 无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
unique_ptr<string> p1(new string("I'm Li Ming!")); unique_ptr<string> p2(new string("I'm age 22.")); cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl; p1 = p2; // 禁止左值赋值 unique_ptr<string> p3(p2); // 禁止左值赋值构造 unique_ptr<string> p3(std::move(p1)); p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样 cout << "p1 = p2 赋值后:" << endl; cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl;
运行截图:
B. 在 STL 容器中使用unique_ptr,不允许直接赋值
vector<unique_ptr<string>> vec; unique_ptr<string> p3(new string("I'm P3")); unique_ptr<string> p4(new string("I'm P4")); vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; vec[0] = vec[1]; /* 不允许直接赋值 */ vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果 cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl;
当然,运行后是直接报错的,因为vec[1]已经是NULL了,再继续访问就越界了。
C. 支持对象数组的内存管理
// 会自动调用delete [] 函数去释放内存 unique_ptr<int[]> array(new int[5]); // 支持这样定义
除了上面ABC三项外,unique_ptr的其余用法都与auto_ptr用法一致。
-
构造
class Test { public: Test() { cout << "Test的构造函数..." << endl; } ~Test() { cout << "Test的析构函数..." << endl; } void doSomething() { cout << "do something......" << endl; } }; // 自定义一个内存释放其 class DestructTest { public: void operator()(Test *pt) { pt->doSomething(); delete pt; } }; // unique_ptr<T> up; 空的unique_ptr,可以指向类型为T的对象 unique_ptr<Test> t1; // unique_ptr<T> up1(new T()); 定义unique_ptr,同时指向类型为T的对象 unique_ptr<Test> t2(new Test); // unique_ptr<T[]> up; 空的unique_ptr,可以指向类型为T[的数组对象 unique_ptr<int[]> t3; // unique_ptr<T[]> up1(new T[]); 定义unique_ptr,同时指向类型为T的数组对象 unique_ptr<int[]> t4(new int[5]); // unique_ptr<T, D> up(); 空的unique_ptr,接受一个D类型的删除器D,使用D释放内存 unique_ptr<Test, DestructTest> t5; // unique_ptr<T, D> up(new T()); 定义unique_ptr,同时指向类型为T的对象,接受一个D类型的删除器D,使用删除器D来释放内存 unique_ptr<Test, DestructTest> t6(new Test);
-
赋值
unique_ptr<Test> t7(new Test); unique_ptr<Test> t8(new Test); t7 = std::move(t8); // 必须使用移动语义,结果,t7的内存释放,t8的内存交给t7管理 t7->doSomething();
-
主动释放对象
unique_ptr<Test> t9(new Test); t9 = NULL; t9 = nullptr; t9.reset();
-
放弃对象的控制权
Test *t10 = t9.release();
-
重置
t9.reset(new Test);
auto_ptr 与 unique_ptr智能指针的内存管理陷阱
auto_ptr<string> p1; string *str = new string("智能指针的内存管理陷阱"); p1.reset(str); // p1托管str指针 { auto_ptr<string> p2; p2.reset(str); // p2接管str指针时,会先取消p1的托管,然后再对str的托管 } // 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错! cout << "str:" << *p1 << endl;
这是由于auto_ptr 与 unique_ptr的排他性所导致的!
为了解决这样的问题,我们可以使用shared_ptr指针指针!
四、shared_ptr
熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?
如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!
例:
class Person { public: Person(int v) { this->no = v; cout << "构造函数 \t no = " << this->no << endl; } ~Person() { cout << "析构函数 \t no = " << this->no << endl; } private: int no; }; // 仿函数,内存删除 class DestructPerson { public: void operator() (Person *pt) { cout << "DestructPerson..." << endl; delete pt; } };
-
引用计数的使用
调用use_count函数可以获得当前托管指针的引用计数。
shared_ptr<Person> sp1; shared_ptr<Person> sp2(new Person(2)); // 获取智能指针管控的共享指针的数量 use_count():引用计数 cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl << endl; // 共享 sp1 = sp2; cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl << endl; shared_ptr<Person> sp3(sp1); cout << "sp1 use_count() = " << sp1.use_count() << endl; cout << "sp2 use_count() = " << sp2.use_count() << endl; cout << "sp2 use_count() = " << sp3.use_count() << endl << endl;
如上代码,sp1 = sp2; 和 shared_ptr< Person > sp3(sp1);就是在使用引用计数了。
sp1 = sp2; --> sp1和sp2共同托管同一个指针,所以他们的引用计数为2;
shared_ptr< Person > sp3(sp1); --> sp1和sp2和sp3共同托管同一个指针,所以他们的引用计数为3;
-
构造
1). shared_ptr< T > sp1; 空的shared_ptr,可以指向类型为T的对象
shared_ptr<Person> sp1; Person *person1 = new Person(1); sp1.reset(person1); // 托管person1
2). shared_ptr< T > sp2(new T()); 定义shared_ptr,同时指向类型为T的对象
shared_ptr<Person> sp2(new Person(2)); shared_ptr<Person> sp3(sp1);
3). shared_ptr<T[]> sp4; 空的shared_ptr,可以指向类型为T[]的数组对象 C++17后支持
shared_ptr<Person[]> sp4;
4). shared_ptr<T[]> sp5(new T[] { … }); 指向类型为T的数组对象 C++17后支持
shared_ptr<Person[]> sp5(new Person[5] { 3, 4, 5, 6, 7 });
5). shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存
shared_ptr<Person> sp6(NULL, DestructPerson());
6). shared_ptr< T > sp7(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存
shared_ptr<Person> sp7(new Person(8), DestructPerson());
-
初始化
1). 方式一:构造函数
shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1 shared_ptr<int> up2(up1); // 使用智能指针up1构造up2, 此时int(10) 引用计数为2
2). 方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:
make_shared<类型>(构造类型对象需要的参数列表);shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个 shared_ptr<string> up4 = make_shared<string>("字符串"); shared_ptr<Person> up5 = make_shared<Person>(9);
-
赋值
shared_ptrr<int> up1(new int(10)); // int(10) 的引用计数为1 shared_ptr<int> up2(new int(11)); // int(11) 的引用计数为1 up1 = up2; // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
-
主动释放对象
shared_ptrr<int> up1(new int(10)); up1 = nullptr ; // int(10) 的引用计数减1,计数归零内存释放 // 或 up1 = NULL; // 作用同上
-
重置
p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
p1是一个指针! -
交换
p1 和 p2 是智能指针std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变 p1.swap(p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
shared_ptr使用陷阱
shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!
如下代码:
Boy类中有Girl的智能指针;
Girl类中有Boy的智能指针;
当他们交叉互相持有对方的管理对象时…
#include <iostream> #include <string> #include <memory> using namespace std; class Girl; class Boy { public: Boy() { cout << "Boy 构造函数" << endl; } ~Boy() { cout << "~Boy 析构函数" << endl; } void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; } private: shared_ptr<Girl> girlFriend; }; class Girl { public: Girl() { cout << "Girl 构造函数" << endl; } ~Girl() { cout << "~Girl 析构函数" << endl; } void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } private: shared_ptr<Boy> boyFriend; }; void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 陷阱用法 spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); // 此时boy和girl的引用计数都是2 } int main(void) { useTrap(); system("pause"); return 0; }
运行截图:
可以看出,程序结束了,但是并没有释放内存,这是为什么呢???
如下图:
当我们执行useTrap函数时,注意,是没有结束此函数,boy和girl指针其实是被两个智能指针托管的,所以他们的引用计数是2
useTrap函数结束后,函数中定义的智能指针被清掉,boy和girl指针的引用计数减1,还剩下1,对象中的智能指针还是托管他们的,所以函数结束后没有将boy和gilr指针释放的原因就是于此。
所以在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!
当然,这也是有办法解决的,那就是使用weak_ptr弱指针。
针对上面的情况,还讲一下另一种情况。如果是单方获得管理对方的共享指针,那么这样着是可以正常释放掉的!
例如:
void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 单方获得管理 //spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); }
反过来也是一样的!
这是什么原理呢?
- 首先释放spBoy,但是因为girl对象里面的智能指针还托管着boy,boy的引用计数为2,所以释放spBoy时,引用计数减1,boy的引用计数为1;
- 在释放spGirl,girl的引用计数减1,为零,开始释放girl的内存,因为girl里面还包含有托管boy的智能指针对象,所以也会进行boyFriend的内存释放,boy的引用计数减1,为零,接着开始释放boy的内存。最终所有的内存都释放了。
五、weak_ptr
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。
-
弱指针的使用;
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针 -
弱指针也可以获得引用计数;
wpGirl_1.use_count() -
弱指针不支持 * 和 -> 对指针的访问;
-
在必要的使用可以转换成共享指针 lock();
shared_ptr<Girl> sp_girl; sp_girl = wpGirl_1.lock(); // 使用完之后,再将共享指针置NULL即可 sp_girl = NULL;
使用代码:
shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); // 弱指针的使用 weak_ptr<Girl> wpGirl_1; // 定义空的弱指针 weak_ptr<Girl> wpGirl_2(spGirl); // 使用共享指针构造 wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针 cout << "spGirl \t use_count = " << spGirl.use_count() << endl; cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl; // 弱指针不支持 * 和 -> 对指针的访问 /*wpGirl_1->setBoyFriend(spBoy); (*wpGirl_1).setBoyFriend(spBoy);*/ // 在必要的使用可以转换成共享指针 shared_ptr<Girl> sp_girl; sp_girl = wpGirl_1.lock(); cout << sp_girl.use_count() << endl; // 使用完之后,再将共享指针置NULL即可 sp_girl = NULL;
当然这只是一些使用上的小例子,具体用法如下:
请看Boy类
#include <iostream> #include <string> #include <memory> using namespace std; class Girl; class Boy { public: Boy() { cout << "Boy 构造函数" << endl; } ~Boy() { cout << "~Boy 析构函数" << endl; } void setGirlFriend(shared_ptr<Girl> _girlFriend) { this->girlFriend = _girlFriend; // 在必要的使用可以转换成共享指针 shared_ptr<Girl> sp_girl; sp_girl = this->girlFriend.lock(); cout << sp_girl.use_count() << endl; // 使用完之后,再将共享指针置NULL即可 sp_girl = NULL; } private: weak_ptr<Girl> girlFriend; }; class Girl { public: Girl() { cout << "Girl 构造函数" << endl; } ~Girl() { cout << "~Girl 析构函数" << endl; } void setBoyFriend(shared_ptr<Boy> _boyFriend) { this->boyFriend = _boyFriend; } private: shared_ptr<Boy> boyFriend; }; void useTrap() { shared_ptr<Boy> spBoy(new Boy()); shared_ptr<Girl> spGirl(new Girl()); spBoy->setGirlFriend(spGirl); spGirl->setBoyFriend(spBoy); } int main(void) { useTrap(); system("pause"); return 0; }
在类中使用弱指针接管共享指针,在需要使用时就转换成共享指针去使用即可!
自此问题完美解决!
六、智能指针的使用陷阱
-
不要把一个原生指针给多个智能指针管理;
int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代码使up1 up2指向同一个内存,非常危险
或以下形式:
up1.reset(x);
up2.reset(x); -
记得使用u.release()的返回值;
在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了. -
禁止delete 智能指针get 函数返回的指针;
如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果! -
禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr< int > sp1(new int(10));
// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());
七、总结
智能指针虽然使用起来很方便,但是要注意使用智能指针的一些陷阱,否则会造成严重的内存报错或者内存泄露等问题!
这篇关于C++ 智能指针 - 全部用法详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享