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;
运行结果如下:
怎么理解内存自动释放了呢: 在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;
}
运行结果:
(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;
}
运行结果如下:
在程序结束时,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;
}
运行结果如下:
可以看到析构函数自动调用了,内存正常释放。
今天的内容就到这儿了。如果对我的推、文有兴趣,欢迎转、载分、享。也可以推荐给朋友关注哦。只推干货,宁缺毋滥。
,