找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

C++11中的智能指针

admin 2019-11-11 09:01 175人围观 C++相关

C++是我平时的工作中用的最多的语言,Python基本是在学习的时候会用,有时候也会用它来写一写脚本。所以,今天准备掺一点C++的知识。

智能指针是C++11标准中的其中一个特性。本文可能需要有一点C++语言的基础。不过尽量用简洁的文字来介绍。如果对C++语言不了解又想学习的话,需要C++学习资料的后台私聊我哦,都是我之前自己学习整理出来的资料。感觉还可以。

在开发C++程序的时候,我们使用new动态的从堆中申请内存,然后使用delete将这段内存释放。使用new申请的内存C++编译器是不会自动释放的。因此,如果我们使用了new来申请内存,但是没有使用delete释放内存,就会造成内存泄漏。如果申请内存的操作是在一个循环中的话,就会不断的造成内存泄漏,最终导致内存不足,程序崩溃。这是很严重的问题。

显然,让程序员来管理内存的释放问题是很繁琐的。有的时候,我们甚至不知道应该在什么时候使用delete来释放内存。比如说在编写比较复杂的多线程程序的时候,申请的内存可能会有多个线程同时访问,可能你自己都无法确定应该合适释放这一块内存。因此,如果能让C++编译器来自动完成内存的分配和释放,那程序员的压力就小很多了。

智能指针内存的分配和释放都是由C++编译器自动完成的。这就是智能指针存在的意义,我们可以将繁琐的内存管理问题交给C++编译器,而将精力放在我们的业务逻辑上。

智能指针的类型

C++11中提出的智能指针有三种类型:shared_ptr、unique_ptr、weak_ptr。使用这三种智能指针的时候需要包含库memory。

shared_ptr

shared_ptr(就是一种指针)管理内存的机制如下:shared_ptr采用引用计数的方式来管理所指向的对象。什么意思呢?举个例子:

现在有一个对象dog,有一个shared_ptr指向它, 此时它的引用计数为1;当有一个新的shared_ptr也指向了dog,那么它的引用计数自动加1,为1;当指向了dog的shared_ptr了离开了它的作用域,引用计数减1,又变为1了。当引用技术为0时(也就是说所有指向dog的shared_ptr都离开了作用域),dog占用的内存自动释放。

还不理解?没关系,看一段代码:

    #include<iostream>#include<string>#include<memory>
    classDog {private: std::string name_;public: Dog(std::string name) {std::cout << "Dog is created." << name << std::endl; name_ = name; } Dog() {std::cout << "Nameless dog created." << std::endl; name_ = "nameless"; } ~Dog() {std::cout << "dog is destroyed: " << name_ << std::endl; }voidbark(){std::cout << "Dog " << name_ << " rules" << std::endl; }};
    voidfoo(){//创建一个指针下面两种方式都可以 //shared_ptr<Dog> p(new Dog("Gunner"));std::shared_ptr<Dog> p = std::make_shared<Dog>("Gunner");//p.use_count==1std::cout << "p->use_count() = " << p.use_count() << std::endl;
    {std::shared_ptr<Dog> p2 = p;//p.use_count==2std::cout << "p->use_count() = " << p.use_count() << std::endl; p2->bark(); } //离开大括号时,p2的作用域结束,p的引用计数减1//p.use_count==1std::cout << "p->use_count() = " << p.use_count() << std::endl; p->bark();}
    intmain(){ foo();}
    首先要注意下面几点:


    • 创建shared_ptr的方式有两种

      • 直接使用new关键字的方式:

        shared_ptr<Dog> p(new Dog("Gunner"));

      • 使用make_shared的方式:

        shared_ptr<Dog> p = make_shared<Dog>("Gunner");

    • shared_ptr、make_shared都是在命名空间std当中,为了避免初学者误会,我直接写成了std::shared_ptr、std::make_shared的方式,而没有使用using namespace std;

    运行结果如下:



    怎么理解内存自动释放了呢: 在foo()函数执行结束之后,智能指针p离开了作用域,它的引用计数减为0了,然后创建的Dog的对象的析构函数自动调用了,输出: dog is destroyed: Gunner。

    上面有几个C++中的重要概念,稍微做一些解释:


    • 命名空间:命名空间也称为名字空间,最通俗的理解就是一个命名的容器,一个空间内的变量、函数、类等的命名不可以相同,但是不同空间的命名可以相同。std是C++编译器的命名空间,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
    • 析构函数: 析构函数和构造函数可以认为是一对函数。构造函数在创建一个类的对象时被自动调用,通常用来做一些初始化的工作。析构函数与构造函数相反,当对象结束其生命周期,如对象离开它的作用域,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

    unique_ptr

    unique是独一无二的意思。unique_ptr的涵义也是相似的,它表达的是一种独占的思想,与shared_ptr最大的区别是unique_ptr不共享它的指针,某个时刻只能有一个unique_ptr指向一个给定的对象。
    创建unique_ptr的方式如下:

    • 使用new关键字:

      std::unique_ptr<Example> ptr(new Example(1));


    • 使用std::make_unique:
      std::unique_ptr<Example> ptr = std::make_unique<Example>(1);

    常用的函数说明:


    • get() : 返回被管理对象的指针

    • release() : 返回指向被管理对象的指针,并释放所有权
    • swap() : 交换被管理对象

    使用示例:
      #include<iostream>#include<vector>#include<memory>usingnamespacestd;
      classExample {public: Example(int param = 0) { number = param;cout << "Example: " << number << endl; }
      ~Example() { cout << "~Example: " << number << endl; }
      voidtest_print(){ cout << "in test print: number = " << number << endl; }
      voidset_number(int num){ number = num; }
      private:int number;};
      voidtest1(){unique_ptr<Example> ptr1 = make_unique<Example>(1);if (ptr1.get()) { ptr1.get()->test_print(); ptr1->set_number(2); (*ptr1).test_print(); }
      unique_ptr<Example> ptr2(new Example(20)); ptr2->test_print();
      ptr1.swap(ptr2); cout << "ptr1和ptr2交换管理对象" << endl; ptr1->test_print(); ptr2->test_print(); }
      intmain(){ test1();return0;}
      运行结果:



      weak_ptr
      std::weak_ptr是一种智能指针。它对被std::shared_ptr管理的对象存在非拥有性(弱)引用。weak_ptr是为了配合shared_ptr而引入的一种智能指针,它不具有普通指针的行为,没有重载运算符*和->,其最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于使得use_count==0,表示被观测的资源(也就是shared_ptr管理的资源)已经不复存在。weak_ptr有一个重要的成员函数lock()可以从被观测的shared_ptr中获得一个可用的shared_ptr对象,从而操作资源。
      weak_ptr被设计用来避免std::shared_ptr的循环引用。

      什么是循环引用问题,下面举个例子说明一下:

      假设现在有两个类A、B,创建了两个智能指针shared_ptr<A> ptr_A、shared_ptr<B> ptr_B分别指向了A、B两个类的对象a、b。A中有个shared_ptr<B>指向b,B中有个shared_ptr<A>指向a。

      下面我们看一下ptr_A、ptr_B的引用计数分别是多少:


      • ptr_A.use_count = 2

      • ptr_B.use_count = 2
      然后程序结束时,ptr_A、ptr_B都离开了它的作用域,引用计数减为1,所以a、b占用的内存不会释放。这就是shared_ptr的缺陷。

      下面可以从一个例子中看一下:

        #include<memory>#include<iostream>
        classfoo;
        classTest {public: Test() {std::cout << "construct.." << std::endl; }
        voidmethod(){std::cout << "welcome Test.." << std::endl; }
        ~Test() {std::cout << "destruct.." << std::endl; }
        public:std::shared_ptr<foo> fooptr;};
        classfoo {public: foo() {std::cout << "foo construct.." << std::endl; }
        voidmethod(){std::cout << "welcome Test foo.." << std::endl; }
        ~foo() {std::cout << "foo destruct.." << std::endl; }
        public:std::shared_ptr<Test> testptr;};
        intmain(){// 循环引用 测试 Test* t2 = new Test(); foo* foo1 = new foo();
        std::shared_ptr<Test> shptr_Test(t2);std::shared_ptr<foo> shptr_foo(foo1);
        std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
        shptr_Test->fooptr = shptr_foo; shptr_foo->testptr = shptr_Test;std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
        return0;}
        运行结果如下:


        在程序结束时,Test类和foo类的析构函数并没有调用。
        使用weak_ptr改进的程序如下:

          #include<memory>#include<iostream>
          classfoo;
          classTest {public: Test() {std::cout << "construct.." << std::endl; }
          voidmethod(){std::cout << "welcome Test.." << std::endl; }
          ~Test() {std::cout << "destruct.." << std::endl; }
          public:std::weak_ptr<foo> fooptr;};
          classfoo {public: foo() {std::cout << "foo construct.." << std::endl; }
          voidmethod(){std::cout << "welcome Test foo.." << std::endl; }
          ~foo() {std::cout << "foo destruct.." << std::endl; }
          public:std::weak_ptr<Test> testptr;};
          int main(){// 循环引用 测试 Test* t2 = new Test(); foo* foo1 = new foo();
          std::shared_ptr<Test> shptr_Test(t2);std::shared_ptr<foo> shptr_foo(foo1);
          std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
          shptr_Test->fooptr = shptr_foo; shptr_foo->testptr = shptr_Test;std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
          return0;}
          运行结果如下:



          可以看到析构函数自动调用了,内存正常释放。

          今天的内容就到这儿了。如果对我的推文有兴趣,欢迎转载分享。也可以推荐给朋友关注哦。只推干货,宁缺毋滥。

          ----------------------------------------------------------------------------------------------------------------------
          我们尊重原创,也注重分享,文章来源于微信公众号:菜鸟的修行之路,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
          ----------------------------------------------------------------------------------------------------------------------

          1人点赞鲜花

          握手

          雷人

          路过

          鸡蛋

          刚表态过的朋友 (1 人)

          yafeilinux和他的朋友们微信公众号二维码

          微信公众号

          专注于Qt嵌入式Linux开发等。扫一扫立即关注。

          Qt开源社区官方QQ群二维码

          QQ交流群

          欢迎加入QQ群大家庭,一起讨论学习!

          我有话说......