c指针的作用(C11中的智能指针)

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。

(1) 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 #include #include class Dog { 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; } void bark() { std::cout << "Dog " << name_ << " rules" << std::endl; } }; void foo() { //创建一个指针下面两种方式都可以 //shared_ptr p(new Dog("Gunner")); std::shared_ptr p = std::make_shared("Gunner"); //p.use_count==1 std::cout << "p->use_count() = " << p.use_count() << std::endl; { std::shared_ptr p2 = p; //p.use_count==2 std::cout << "p->use_count() = " << p.use_count() << std::endl; p2->bark(); } //离开大括号时,p2的作用域结束,p的引用计数减1 //p.use_count==1 std::cout << "p->use_count() = " << p.use_count() << std::endl; p->bark(); } int main() { foo(); }

首先要注意下面几点:

  • 创建shared_ptr的方式有两种
    • 直接使用new关键字的方式: shared_ptr p(new Dog("Gunner"));
    • 使用make_shared的方式:shared_ptr p = make_shared("Gunner");
  • shared_ptr、make_shared都是在命名空间std当中,为了避免初学者误会,我直接写成了std::shared_ptr、std::make_shared的方式,而没有使用using namespace std;

运行结果如下:

c指针的作用(C11中的智能指针)(1)

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

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

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

(2) unique_ptr

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

创建unique_ptr的方式如下:

  • 使用new关键字:std::unique_ptr ptr(new Example(1));
  • 使用std::make_unique:std::unique_ptr ptr = std::make_unique(1);

常用的函数说明:

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

使用示例:

#include #include #include using namespace std; class Example { public: Example(int param = 0) { number = param; cout << "Example: " << number << endl; } ~Example() { cout << "~Example: " << number << endl; } void test_print() { cout << "in test print: number = " << number << endl; } void set_number(int num) { number = num; } private: int number; }; void test1() { unique_ptr ptr1 = make_unique(1); if (ptr1.get()) { ptr1.get()->test_print(); ptr1->set_number(2); (*ptr1).test_print(); } unique_ptr ptr2(new Example(20)); ptr2->test_print(); ptr1.swap(ptr2); cout << "ptr1和ptr2交换管理对象" << endl; ptr1->test_print(); ptr2->test_print(); } int main() { test1(); return 0; }

运行结果:

c指针的作用(C11中的智能指针)(2)

(3) 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 ptr_A、shared_ptr ptr_B分别指向了A、B两个类的对象a、b。A中有个shared_ptr指向b,B中有个shared_ptr指向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 #include class foo; class Test { public: Test() { std::cout << "construct.." << std::endl; } void method() { std::cout << "welcome Test.." << std::endl; } ~Test() { std::cout << "destruct.." << std::endl; } public: std::shared_ptr fooptr; }; class foo { public: foo() { std::cout << "foo construct.." << std::endl; } void method() { std::cout << "welcome Test foo.." << std::endl; } ~foo() { std::cout << "foo destruct.." << std::endl; } public: std::shared_ptr testptr; }; int main() { // 循环引用 测试 Test* t2 = new Test(); foo* foo1 = new foo(); std::shared_ptr shptr_Test(t2); std::shared_ptr 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; return 0; }

运行结果如下:

c指针的作用(C11中的智能指针)(3)

在程序结束时,Test类和foo类的析构函数并没有调用。

使用weak_ptr改进的程序如下:

#include #include class foo; class Test { public: Test() { std::cout << "construct.." << std::endl; } void method() { std::cout << "welcome Test.." << std::endl; } ~Test() { std::cout << "destruct.." << std::endl; } public: std::weak_ptr fooptr; }; class foo { public: foo() { std::cout << "foo construct.." << std::endl; } void method() { std::cout << "welcome Test foo.." << std::endl; } ~foo() { std::cout << "foo destruct.." << std::endl; } public: std::weak_ptr testptr; }; int main() { // 循环引用 测试 Test* t2 = new Test(); foo* foo1 = new foo(); std::shared_ptr shptr_Test(t2); std::shared_ptr 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; return 0; }

运行结果如下:

c指针的作用(C11中的智能指针)(4)

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

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

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com