• 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 }
  • 相关阅读:
    JavaScript中get和set访问器的实现
    Vue render 函数和JSX语法的使用
    vue项目将第三方包以cdn的方式引入页面不显示
    今天帮忙HMF这位ui大神(傻)解决的小问题。
    SET
    webpack 折腾
    sass折腾
    gulp折腾
    css3
    js递归函数
  • 原文地址:https://www.cnblogs.com/jianhui-Ethan/p/4665550.html
Copyright © 2020-2023  润新知