C++ 资源管理(RAII)--智能指针
1. 智能指针(Smart Pointer)
i. 是存储指向动态分配(堆)对象指针的类
ii. 在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象
iii. RAII类模拟智能指针,见备注
2. C++11提供了以下几种智能指针,位于头文件<memory>,它们都是模板类
i. std::auto_ptr(复制/赋值)
ii. std::unique_ptr c++11
iii.std::shared_ptr c++11
iv.std::weak_ptr c++11
g++ -std=c++11 xx.cc
3. std::auto_ptr在构造时获取对某个对象的所有权(ownership),在析构时释放该对象
4. std::auto_ptr要求其对“裸”指针的完全占有性 -> 在拷贝构造或赋值操作时,会发生所有权的转移
5. 本身存在缺陷
6. std::unique_ptr是一个独享所有权的智能指针,它提供了一种严格语义上的所有权,包括:
i. 拥有它所指向的对象
ii. 无法进行复制、赋值操作(例题)
iii.保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象
iv.具有移动(std::move)语义,可做为容器元素
7. std::shared_ptr是一个引用计数智能指针,用于共享对象的所有权
i. 引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块
ii. 析构函数中不是直接释放指针对应的内存块,如果shared_count大于0则不释放内存只是将引用计数减1,只是计数等于0时释放内存
iii. 复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加1.
iv. 在堆内存中只有一份申请的资源,但是有好几个对象都是指向这个内存的资源的。
v. Shared_ptr的功能就是让不具有值语义的对象拥有值语义,假如说一个对象本身是不希望被复制的,但是要是把这个对象交给shared_ptr管理的时候,使用这个指针的时候就可以对对象进行复制,实际不是真实的对象复制,而是通过一个引用计数的方式去使用。
8. 问题:会有一个问题就是循环引用,会导致内存泄漏。
9. std::shared_ptr是强引用智能指针
10. std::weak_ptr 是弱引用智能指针
11. 强引用,只要有一个引用存在,对象就不能被释放
12. 弱引用,并不增加对象的引用计数,但它知道对象是否存在。如果存在,提升为shared_ptr成功;否则,提升失败
13. 通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
1 auto_ptr.cc 2 3 #include<iostream> 4 #include<memory> 5 6 int main(void) 7 { 8 double *pd = new double(7.77); 9 std::auto_ptr<double> apd(pd); //apd本身是个对象,因为它重载了星号访问运算符,所以可以加* 用。 10 //或者std::auto_ptr<double> apd(new double(7.77)); 11 12 std::cout << “*apd=” << *apd <<std::endl; 13 //通过* 去访问的时候,就相当于对他所托管的指针(pd)所指向的对象进行访问 . 14 15 //std::cout << “apd = ” << apd << std::endl; //会出错,对象不能这样直接打印 16 17 std::cout << “apd.get() = ” << reinterpret_cast<long>(apd.get()) << std::endl; 18 19 double *pd = new double(8.88); 20 std::auto_ptr<double> apd2(pd); 21 std::cout << “pd = ” << reinterpret_cast<long>(pd) << std::endl; 22 std::cout << “apd2.get() = ” << reinterpret_cast<long>(apd2.get()) << std::endl; //通过get()可以获得原生的指针所在的地址。 23 24 int *pi = new int(7); 25 std::auto_ptr<int> api1(pi); 26 std::auto_ptr<int> api2(api1); //复制, 发生了所有权的转移。首先把api1裸指针所指向的值交给了api2, 同时又把api1所指向的值设为了空。相当于api1对pi的所有权完全交给了api2。发生所有权的转移,与常规的认识矛盾。本身有缺陷,不推荐使用。 27 // std::auto_ptr要求其对“裸”指针的完全占有性在拷贝构造或赋值操作时,会发生所有权的转移 28 29 std::cout << “*api1= ” << *api1 <<std::endl; //现在访问api1指向的值,发现没有了,发生一个段错误。 30 std::cout << “*api2 = ” << *api2 <<std::endl; 31 32 33 return 0; 34 }
1 unique_ptr.cc 2 3 #include<iostream> 4 #include<memory> 5 #include<vector> 6 7 8 std::unique_ptr<int> getVal() //这里返回一个unique_ptr对象,这个对象是个右值。 9 { 10 std::unique_ptr<int> up(new int(66)); 11 return up; 12 } 13 14 int main(void) 15 { 16 // 无法进行复制、赋值操作 17 std::unique_ptr<int> ap(new int(99)); 18 //std::unique_ptr<int> one(ap); //编译出错,不能够进行复制。 19 20 std::unique_ptr<int> two; 21 //two = ap; //编译出错,不能进行赋值。 22 23 std::cout << “*ap = ” << *ap << std::endl; 24 std::cout << “ap.get() = ” << reinterpret_cast<long>(ap.get()) << std::endl; //获取指针的值 25 26 //可以进行移动构造和移动赋值操作 27 std::unique_ptr<int> up = getVal(); //getVal()函数返回一个右值,这个右值会优先绑定到右值引用上去。unique_ptr是具有移动语义的,意思就是说它提供了一个移动构造函数和一个移动赋值函数。而这里就优先调用了移动赋值函数。并没有调用复制构造函数。 28 std::cout << “*up = ” << *up << std::endl; 29 30 31 //实际上上面的操作有点类似于如下操作 32 Unique_ptr<int> up(int new int(99)); 33 Unique_ptr<int> uPtr2 = std::move(up); //这里是显式的所有权转移。把up所指的内存转给uPtr2了,而up不再拥有该内存。 34 35 36 37 //可以作为容器的元素(就是因为具有移动语义) 38 std::unique_ptr<int> sp(new int(55)); //sp现在是一个左值,就要绑定到复制构造函数上面去,因为复制构造函数的参数是一个左值引用。 39 std::vector<std::unique_ptr<int> vec; 40 //vec.push_back(sp); //会出错,因为会调用复制构造函数 41 vec.puch_back(std::move(sp)); //将左值转成右值引用,这时就会调用 移动构造函数,而不是 复制构造函数。 42 std::cout << *vec[0] << std::endl; //打印刚添加的值。 43 44 return 0; 45 }
1 Shared_ptr.cc 2 3 #include<iostream> 4 #include<memory> 5 6 class Child; 7 class Parent; 8 9 typedef std::shared_ptr<Child> Child_ptr; 10 typedef std::shared_ptr<Parent> Parent_ptr; 11 12 class Child 13 { 14 public: 15 Child() 16 { 17 std::cout << “Child()” << std::endl; 18 } 19 ~Child() 20 { 21 std::cout << “~Child()” << std::endl; 22 } 23 //private: 24 Parent_ptr parent_; 25 }; 26 27 class Parent 28 { 29 public: 30 Parent() 31 { 32 std::cout << “Parent()” << std::endl; 33 } 34 ~Parent() 35 { 36 std::cout << “~Parent()” << std::endl; 37 } 38 39 //private: 40 Child_ptr child_; 41 }; 42 43 int main(void) 44 { 45 Parent_ptr parent(new Parent); //交给shared_ptr进行管理 46 Child_ptr child(new Child); 47 48 std::cout << “parent’s count = ” << parent.use_count() << std::endl; 49 std::cout << “child’s count = ” << child.use_count() << std::endl; 50 51 std::cout << “进行复制之后:” << std::endl; 52 parent->child_ = child; //shared_ptr复制操作 53 std::cout << “child’s count = ” << child.use_count() << std::endl; //打印出引用计数为2. 54 child->parent_ = parent; 55 std::cout << “parent’s count = ” << parent.use_count() << std::endl; //打印出引用计数为2. 56 57 return 0; 58 } 59 //因为相互引用,当程序结束时,引用计数都减一,都成为1。内存中还有这两个对象的存在,就不会调用析构函数。这样就带来了内存泄漏,是循环引用存在的问题。
1 weak_ptr1.cc 2 3 #include<iostream> 4 #include<memory> 5 6 class Child; 7 class Parent; 8 9 typedef std::shared_ptr<Child> Child_ptr; 10 typedef std::shared_ptr<Parent> Parent_ptr; 11 12 class Child 13 { 14 public: 15 Child() 16 { 17 std::cout << “Child()” << std::endl; 18 } 19 ~Child() 20 { 21 std::cout << “~Child()” << std::endl; 22 } 23 //private: 24 Parent_ptr parent_; 25 }; 26 27 class Parent 28 { 29 public: 30 Parent() 31 { 32 std::cout << “Parent()” << std::endl; 33 } 34 ~Parent() 35 { 36 std::cout << “~Parent()” << std::endl; 37 } 38 39 std::weak_ptr<Child> child_; //弱引用 40 }; 41 42 int main(void) 43 { 44 Parent_ptr parent(new Parent); //交给shared_ptr进行管理 45 Child_ptr child(new Child); 46 47 std::cout << “parent’s count = ” << parent.use_count() << std::endl; 48 std::cout << “child’s count = ” << child.use_count() << std::endl; 49 50 std::cout << “进行复制之后:” << std::endl; 51 parent->child_ = child; //child_是weak_ptr,引用计数并没有加1 52 std::cout << “child’s count = ” << child.use_count() << std::endl; //打印出引用计数为2. 53 child->parent_ = parent; 54 std::cout << “parent’s count = ” << parent.use_count() << std::endl; //打印出引用计数为2. 55 56 return 0; 57 } 58 //因为使用的是弱引用,复制的时候引用计数不会加1. 程序结束时会调用析构函数。
1 Weak_ptr.cc
2 #include<iostream> 3 #include<memory> 4 5 class X 6 { 7 public: 8 X() {std::cout << “X()” << std::endl;} 9 ~ X() {std::cout << “~X()” << std::endl;} 10 11 void fun() 12 { 13 std::cout << “fun()” << std::endl; 14 } 15 }; 16 17 int main(void) 18 { 19 std::weak_ptr<X> p; 20 { 21 std::shared_ptr<X> p2(new X); //所有new出来的东西都放到这个栈对象p2里面,因为当这个语句块结束的时候p2会调用析构函数,我们在析构函数中加上delete来释放托管过来的指针。 22 std::cout << “p2’s count = ” << p2.use_count() <<std::endl; 23 24 p = p2; //std::weak_ptr 不会增加引用计数 25 std::cout << “after p = p2 ” <<std::endl; 26 std::cout << “p2’s count = ” << p2.use_count() <<std::endl; 27 28 std::shared_ptr<X> p3 = p.lock(); //lock()函数就是用来提升weak_ptr为shared_ptr的函数。 29 30 if(p3) //提升成功 31 { 32 p3->fun(); 33 std::cout << “p3’s count = ” << p3.use_count() <<std::endl; 34 } 35 else //提升失败 36 { 37 std::cout << “object has been destroied” << std::endl; 38 } 39 } 40 41 //new X 已经被释放了 42 //通过weak_ptr访问对象的成员的时候,要提升为shared_ptr 43 std::shared_ptr<X> p4 = p.lock(); 44 if(p4) //提升成功 45 { 46 P4->fun(); 47 std::cout << “p4’s count = ” << p4.use_count() <<std::endl; 48 } 49 else //提升失败 50 { 51 std::cout << “object has been destroied” << std::endl; 52 } 53 54 //智能指针作为栈对象来使用,不要采用堆对象的方式来使用。因为智能指针作为栈对象来使用,它可以具有自动管理、自动回收的功能。 55 //智能指针的实现原理:栈对象生命周期结束的时候,会自动调用析构函数。 56 //std::shared_ptr<X> *pthis = new std::shared_ptr<X>(new X); 57 //上述方式就是生成堆对象,不推荐这么做。而且这时候只能通过显示调用delete函数,这样就跟我们的初衷不符。 58 59 return 0; 60 }