C++中的智能指针
一、为什么需要智能指针?
在c++中内存管理中,有一个需要程序员手动分配与释放的内存空间叫做堆。我们可以使用new 运算符来生成一个动态对象并为其在在堆上分配空间。由于堆得空间大小是有限的,当我们不再使用动态对象时,需要程序员手动释放该内存空间。
new,在动态内存中(堆)为对象分配空间并返回一个指向该对象的指针,可以选择对对象初始化。
delete, 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的管理是很复杂的,很容易造成内存泄漏以及引用非法内存的指针。所以标准库提供了两种智能指针,来方便对动态内存的使用。
二、shared_ptr
2.1 shared_ptr的定义与引用计数的内涵
从shared可以看出该类型智能指针允许多个指针指向同一对象。
下面是默认初始化的智能指针的定义形式,没有初始值的智能指针默认初始化为空。
1 shared_ptr<int> s1; 2 shared_ptr<vector<int>> s2;
可以使用make_shared<type>(初始值)这种形式来对shared_ptr进行初始化。
shared_ptr<int> s1 = make_shared<int>(); //动态对象初始化为0 注意这种只有()的初始化方式 shared_ptr<string> s2 = make_shared<string>("Test"); // 动态对象初始化为“Test”
shared_ptr既然是可以允许多个指针指向同一对象。那么就会有它所指那个对象的引用计数(代表有多少指针指向了该动态对象,一旦引用计数为0时,该动态对象才会被销毁)
下面这段代码可以体现这一点,代码的结果如图所示。指针s1指向一个动态分配的对象,然后在定义一个s2并利用s1初始化。那么use_count()的含义是该智能指针所指向的动态对象有多少引用(通俗一点就是:多少指针指向该动态对象)
1 #include<iostream> 2 #include<memory> 3 using namespace std; 4 int main(void) { 5 shared_ptr<int> s1 = make_shared<int>(10); 6 cout << "s1.use_count() = " << s1.use_count() << endl; 7 shared_ptr<int> s2 = s1; 8 cout << "s1.use_count() = " << s1.use_count() << endl; 9 cout << "s2.use_count() = "<< s2.use_count() << endl; 10 getchar(); 11 return 0; 12 }

同理,当一个shared_ptr指针被重新赋值,那么它之前所指的动态对象的引用计数减一,而赋值给它的那个指针所指的动态对象引用计数加一。
1 #include<iostream> 2 #include<memory> 3 4 using namespace std; 5 6 int main(void) { 7 shared_ptr<int> s1 = make_shared<int>(10); 8 shared_ptr<int> s2 = s1; 9 cout << "s1.use_count() = " << s1.use_count() << endl; 10 cout << "s2.use_count() = " << s2.use_count() << endl; 11 shared_ptr<int> s3 = make_shared<int>(9); 12 s1 = s3; //对s1重新赋值 13 cout << "s1.use_count() = " << s1.use_count() << endl; 14 cout << "s2.use_count() = "<< s2.use_count() << endl; 15 cout << "s3.use_count() = " << s3.use_count() << endl; 16 getchar(); 17 return 0; 18 }

当s1重新赋值,s1和s3引用计数相同,且s2引用计数减去1.因为s2和s1之前指向相同的动态对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象(通过一个特殊的成员函数--析构函数)。
2.2 shared_ptr与new运算符
利用new返回的指针来初始化shared_ptr
shared_ptr<int> s1 = new int(2); //错误 shared_ptr<int> s2(new int(2)); //直接初始化方式正确
因为接受指针参数的智能指针构造函数是explicit的,因此不能讲一个内置指针隐式转换为一个智能指针,必须使用直接初始化。
2.3 智能指针与普通指针混用以及shared_ptr.get()
在上面我们提到可以用new返回的运算符给shared_ptr初始化,但是在实际使用过程中最好不要这么做。如下代码:
1 #include<iostream> 2 #include<memory> 3 using namespace std; 4 int main(void) { 5 int *p1 = new int(3); 6 shared_ptr<int> s1(p1); 7 cout << "s1.use_count() = " << s1.use_count() << endl; //虽然s1和p1指向相同的内存,但是引用计数只有1. 8 s1.reset(); 9 cout << "s1.use_count() = " << s1.use_count() << endl; //释放了s1所指向的内存,这样p1指向就是非法内存 10 cout << *p1 << endl; //所以*p1的值就是未定义的。 11 getchar(); 12 return 0; 13 }

从上面的例子可以看出,shared_ptr的引用计数含义是:指向该动态分配的内存的智能指针个数(非智能指针不包括在内),一旦引用计数为0,则该动态分配的内存就会释放掉,无论是否有其他普通指针指向该动态内存。
我们再来看一下shared_ptr的get()方法,该方法返回一个普通指针(和它所属的智能指针指向同一块内存空间),先看一段代码结果:
#include<iostream> #include<memory> using namespace std; int main(void) { shared_ptr<int> s1 = make_shared<int>(3); shared_ptr<int> s2(s1.get()); cout << "s1.use_count() = " << s1.use_count() << endl; //s1和s2引用计数都为1.尽管是指向同一内存空间。 cout << "s2.use_count() = " << s2.use_count() << endl; //因为系统认为这是独立创建的两个智能指针,尽管指向同一内存。 s1.reset(); //释放s1指向的内存,那么s2指向的就变成了了非法内存。但是s2的引用计数不会为0. cout << "s1.use_count() = " << s1.use_count() << endl; cout << "s2.use_count() = " << s2.use_count() << endl; cout << *s2 << endl; //非法内存取值,是一个未定义的值。 getchar(); return 0; }

从上面结果可以看出, 由于s1和s2是相互独立创建的,所以会认为是两个没有关系的智能指针,尽管指向同一内存空间。
三、unique_ptr
3.1 初始化
1 #include<iostream> 2 #include<memory> 3 using namespace std; 4 5 int main(void) { 6 unique_ptr<int> u1; //默认为空指针 7 unique_ptr<int> u2(new int(3)); //最普遍的定义方式 8 unique_ptr<int> u3(u2.get()); //尽管指向同一内存,但是是不同的智能指针,逻辑上还是一个指针指向一个“特定的”内存空间。 9 10 //错误的定义与赋值(这是允许的) 11 unique_ptr<int> u4 = new int(4); //错误,不支持拷贝初始化 12 unique_ptr<int> u5 = u4; //错误,不支持拷贝 13 unique_ptr<int> u6(u5); //错误,也不支持这种形式直接初始化 14 unique_ptr<int> u7 = make_shared<int>(5); //错误,只允许直接初始化,且make_shared方式不能用于unique_ptr的定义。 15 getchar(); 16 return 0; 17 }
unique_ptr初始化规则
- 只支持直接初始化形式或不初始化(为nullptr)
- 不支持需要调用拷贝构造函数的初始化形式
- 不支持赋值
3.2 传递unique_ptr参数和返回unique_ptr
上面提到,不能赋值和拷贝。但是作为函数参数或返回是可以的。即可以拷贝或赋值一个将要被销毁的unique_ptr.只要保证任意时刻只有一个unique_ptr指向同一内存空间(u_pt.get()这种除外)
unique_ptr<int>clone(int p){ return unique_ptr<int>(new int(p)); } unique_ptr<int>clone(int p){ unique_ptr<int> ret (new int(p); return ret; }
3.3 release() and reset()
release()用于释放对指针的控制权,本指针置空。且返回一个指针。
1 #include<iostream> 2 #include<memory> 3 using namespace std; 4 5 int main(void) { 6 unique_ptr<int> u1(new int(3)); 7 unique_ptr<int> u2(u1.release()); //u1置空,且返回所指向的指针给u2。 8 cout << *u2 << endl; //u2接管了u1所指向的对象。输出3 9 u1.reset(new int(4)); //u1重置,为一个new新对象 10 cout << *u2 << endl; 11 u2.reset(); 12 if (u2 == nullptr) 13 cout << "u2 == nullptr" << endl; 14 else 15 cout << "u2 != nullptr" << endl; 16 getchar(); 17 return 0; 18 }
我们可以用reset重置指针,可以重置为为空,也可以为new出来的对象,也可以为release().

四、weak_ptr
weak_ptr是一种不控制所指向对象生命期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁。无论是否weak_ptr指向该对象。
1 #include<iostream> 2 #include<memory> 3 using namespace std; 4 5 int main(void) { 6 shared_ptr<int> s1(new int(3)); 7 weak_ptr<int> w1 = s1; 8 weak_ptr<int> w2 = w1; 9 cout << "s1.use_count() = " << s1.use_count() << endl; //w1 和 w2 不会增加s1的引用计数,但三种相等 = 1。 10 cout << "w1.use_count() = " << w1.use_count() << endl; 11 cout << "w2.use_count() = " << w2.use_count() << endl; 12 weak_ptr<int> w3 = make_shared<int>(3); 13 cout << "w2.use_count() = " << w2.use_count() << endl; 14 //shared_ptr<int> s2 = w2; //错误,并不能从weak_ptr to shared_ptr 15 w3.reset(); //w3 == nullptr 16 if (w3.expired()) //判断w3.use_count()是否为0 17 cout << "w3.use_count() == 0" << endl; 18 else 19 cout << "w3.use_count() != 0" << endl; 20 weak_ptr<int> w4 = w3.lock(); //返回w3所指向的对象,若不存在则返回nullptr 21 weak_ptr<int> w5 = w2.lock(); 22 if (w4.expired()) 23 cout << "w4 == nullptr" << endl; 24 else 25 cout << "w4 != nullptr" << endl; 26 if (w5.expired()) 27 cout << "w5 == nullptr" << endl; 28 else 29 cout << "w5 != nullptr" << endl; 30 getchar(); 31 return 0; 32 }
浙公网安备 33010602011771号