1.应用与原理
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。c++通过new和delete来处理动态内存的申请和释放,但是new之后处理不好delete便会导致内存泄漏。但是智能指针的删除是智能指针本身决定的,用户不需要管理释放。
智能指针的封装是一个模板,所以使用的方式是shared_ptr<int>,shared_ptr<string>这种格式。
2.智能指针的类型
[1]shared_ptr,shared_ptr类的本质就是维护了一个引用计数,所以可以有很多shared_ptr同时指向这个内存,每次使用时计数加1,不使用了计数减1,减到0时自动销毁对象。
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; void fun() { auto tmp = make_shared<int>(6); return ; //离开作用域之后,tmp指向的内存计数减为0,因为自动释放内存 } int main(int argc, char *argv[]) { // shared_ptr的定义 shared_ptr<int> i1; // 空指针 shared_ptr<int> i2 = make_shared<int>(2); //指向一个值为2的int指针,即*i2 是2 auto i3 = make_shared<int>(3); //auto自动设置类型 shared_ptr<string> s1 = "s1"; //shared_ptr的拷贝和赋值 auto i4 = i3; //递增i3指向的int内存引用计数 i3 = i1; //递减i3指向的int内存引用计数,递增i1指向的int内存引用计数 fun(); return 0; }
[2]unique_ptr,这个指针独享一个指向的对象,就是说只能有一个unique_ptr指向一个对象,不能被多个同时指向而已。这个因为不能赋值和拷贝,所以需要用两个函数来处理这些操作:
release():调用后会释放所有权,并返回所指向对象的指针。从这里可以看到release只是释放对象的所有权,并没有释放对象(而是返回对象的指针),所以必须要接管这个对象指针,否则会造成内存泄漏。
s1.release(); //错误,s1指向的内存没有释放,这个内存指针也无法再获取到。正确的应该是s2 = s1.release();
reset():调用会释放内存(如果unique_ptr不为空),并重新指向一个新的指针。
s1.reset(s2.release()); //首先s2释放所有权,返回一个指针,然后s1释放当前指向的内存(如果s1不为空),并接管这个s2返回的指针。
s1.reset(); //s1释放当前指向的内存(如果s1不为空),并把s1置空。
这个意思就是release()只是释放所有权,并且需要其他的变量接管这个对象,不接管会内存泄漏。reset就是释放内存,如果有参数,则指向把这个参数指向的对象。
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; int main(int argc, char *argv[]) { // unique_ptr的定义 unique_ptr<int> i1; // 空指针 unique_ptr<int> i2(new int(2)); //指向一个值为2的int指针,即*i2 是2 i1 = i2; //错误,不能被多个指向 // release和reset的使用 unique_ptr<int> s1(new string("s1")); unique_ptr<int> s2(s1.release()); //s1.release() : s1所有权释放后被置空,然后把所有权赋值给s2 unique_ptr<int> s3(new string("s3")); s2.reset(s3.release()); //s3.release() : s3所有权释放后被置空,然后s2当前的所有权被释放,然后把所有权赋值给s2 return 0; }
unique_ptr的例外情况:可以传递unique_ptr参数和返回unique_ptr。这个意思就是说unique_ptr可以当成函数参数传递,因为传参就有拷贝的操作,也可以当成函数的返回值,因为返回一个值,也会有拷贝的操作。但是这两种情况是允许的,例如下面的例子:
unique_ptr<int> fun(int p) { return unique_ptr<int>(new int(p)); }
[3]weak_ptr,是一种不控制所指对象生存期的智能指针,指向一个有shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会增加所指对象的引用计数。所以访问weak_ptr时,对象可以已经被删除,因此访问weak_ptr时,需要调用lock()函数,这个lock()函数返回一个指向共享对象的shared_ptr,因此会可以正确使用这个shared_ptr,因为返回shared_ptr会加指向对象的计数。
总结一下就是weak_ptr不保证指向的对象存在,但是可以通过lock()函数随时获取,获取到了就可以使用,获取不到(lock()返回空)就说明对象被释放了。参考下面的例子:
shared_ptr<int> i1(new int(10)); weak_ptr<int> i2 = i1; { shared_ptr<int> i3 = i2.lock(); //lock确实是加锁的意思,只不过这里是通过增加i2指向的内存的引用计数(返回一个shared_ptr)来实现加锁 if(i3) printf("weak ptr i2 is valid.\n"); else printf("weak ptr i2 is invalid.\n"); //i3只在此作用域,出了此作用域,i3指向的对象便会减计数,因此会自动解锁 }
3.手动实现一个shared_ptr
这个主要是用到了引用计数的方式。
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> using namespace std; //每个smart_ptr都有一个ptr和use_count,其中ptr指向共享的对象。use_count指向共享对象的引用计数。 //这个计数当然是需要共享一份。如果各自有各自的计数(例如使用int use_count;),那肯定出问题。 template<class T> class smart_ptr { public: smart_ptr(T *p); ~smart_ptr(); smart_ptr(const smart_ptr<T> &orig); smart_ptr<T>& operator=(const smart_ptr<T> &rhs); private: T *ptr; int *use_count; }; // 常规的构造函数 template<class T> smart_ptr<T>::smart_ptr(T *p) : ptr(p) { use_count = new int(1); printf("smart_ptr(T *p) is called!"); } //带参数的构造函数,适应这种初始化smart_ptr<int> p2(p1); template<class T> smart_ptr<T>::smart_ptr(const smart_ptr<T> &orig) { ptr = orig.ptr; use_count = orig.use_count; ++(*use_count); printf("smart_ptr(const smart_ptr<T> &orig) is called!"); } template<class T> smart_ptr<T>::~smart_ptr() { if (--(*use_count) == 0){ delete ptr; delete use_count; ptr = NULL; use_count = NULL; printf("~smart_ptr() is called!"); } } //重载"="操作符:增加右侧计数,减少左侧计数。 template<class T> smart_ptr<T>& smart_ptr<T>::operator=(const smart_ptr<T> &rhs) { ++(*rhs.use_count); if (--(*use_count) == 0){ delete ptr; delete use_count; printf("operator=(const smart_ptr<T> &rhs) is called, delete left side.\n"); } ptr = rhs.ptr; use_count = rhs.use_count; printf("operator=(const smart_ptr<T> &rhs) is called.\n"); return *this; } int main() { smart_ptr<int> p1(new int(0)); p1 = p1; smart_ptr<int> p2(p1); smart_ptr<int> p3(new int(1)); p3 = p1; return 0; }