C++11特性-智能指针
1.智能指针
1.1内存泄露
内存泄露是指因为疏忽或者程序的异常错误等原因,导致本应该释放的堆内存没有完成释放,这样这块堆内存就无法再被使用,称之为泄露。
如果长时间发生内存泄漏,会导致程序响应越来越慢,最终卡死。
我们主要关心两方面的内存泄露:1.堆内存,要记得释放。2.系统资源也需要释放,比如说通信套接字、文件描述符、管道等等。
#include <iostream>
#include <vector>
#include <string>
#include <memory>
using namespace std;//1.1内存泄露
void MemoryLeaks()
{//1.由于自己的疏忽,忘记了释放堆内存int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;//用完之后没有通过free和delete释放,就会造成泄露//2.异常造成的内存无法释放int* p3 = new int[10];//这里运行了一系列的代码,如果这里的代码抛出了异常,那就会导致后面的代码不执行,导致p3没有释放delete[] p3;
}
1.2智能指针
内存泄露是C和C++编程非常麻烦的地方,所以后来出现了智能指针技术,可以通过智能指针来管理内存和系统资源,实现自动释放的效果。
智能指针的原理:
1) 智能指针采用的是一种叫做RAII的技术,叫做资源获取即初始化技术。RAII是一种利用对象的生命周期来控制程序资源(内存、文件句柄、套接字等等)的技术。
2) 具体来说就是使用一个对象,在对象构造的时候来获取资源;在对象的有效生命周期内,控制对资源的访问;在对象析构的时候来释放资源。巧妙的把资源管理的责任委托给了对象。代码层面的实现方式,就是创建一个智能指针类,类内重载指针的运算符如 * 和 -> 等,这样这个类就可以像使用指针一样来使用了。
3) 同时,这个智能指针类必须存储指向我们要管理的资源的原始指针,这样才可以管理内存等资源,实现自动释放的效果。
/1.2智能指针
//定义一个智能指针类,决定采用模板类
template<typename T>
class SmartPtr
{T* ptr;//原始指针
public:SmartPtr(T* _ptr):ptr(_ptr){}//构造函数~SmartPtr(){//实现自动释放内存的逻辑if (ptr!=nullptr){delete ptr;//这里就回收了内存ptr = nullptr;//再次置空,否则就会成为野指针cout << "智能指针回收了内存" << endl;}}
};
void test01()
{SmartPtr<int> ptr_int(new int(1));SmartPtr<string> ptr_string(new string("abc"));
}
1.3智能指针分类
首先引入头文件#include <memory>
智能指针根据是否拥有一个对象的所有权,分为三种类型:
1)共享智能指针,shared_ptr:多个智能指针共享一个对象的所有权。
2)独占智能指针,unique_ptr:只能由智能指针独占这个对象。
3)弱共享智能指针,weak_ptr:它不具有对象的所有权,也就不能操作资源,它是打辅助的,用来监视资源,配合共享指针来使用。
1)共享智能指针:
允许多个智能指针通过拷贝构造或者赋值的方式,来同时管理一块内存。它们共享一个引用计数,这是关键技术。每个智能指针管理这块内存的时候,引用计数都会+1,每减少一个智能指针管理的时候,引用计数-1,当引用计数减到0的时候,内存就会被释放掉。这样保证内存只会被释放一次。
共享智能指针初始化的方式:
构造函数:shared_ptr<T> sp(要管理的内存地址指针);
拷贝构造:shared_ptr<T> sp(另一个已经存在的智能指针对象);此时这两个智能指针就完成了对一块内存的共享;
辅助函数:shared_ptr<int> sp1=make_shared<int>(100);
共享智能指针其他的成员函数:
1)use_count();查看引用计数,即代表有多少个智能指针在同时管理这块内存。
2)reset();重置指针,不带参数的时候,重置当前智能指针,如果此时它是该内存的唯一管理者,还需要释放内存。即最后一个走的负责关门。如果它不是最后一个管理者,只需要引用计数-1。
3)reset(要管理的新的内存地址);重置之后马上管理新内存。其他逻辑跟上面一样。
4)get();返回智能指针管理的原始指针,也叫做裸指针。
5)operator=():赋值运算符的重载函数,用来完成智能指针之间的赋值;此时这两个智能指针就完成了对一块内存的共享。
//1)共享智能指针
//准备一个类,后面给智能指针来管理
class Test
{int m_num;
public:Test() { cout << "Test类的无参构造" << endl; }Test(int x) { m_num = x; cout << "Test类的有参构造,参数=" <<x<< endl; }~Test(){ cout << "Test类的析构" << endl; }void setValue(int v) { m_num = v; }void print() { cout << "m_num=" << m_num << endl; }
};
void test02()
{//构造共享指针shared_ptr<int> ptr(new int(1));cout << ptr << endl;cout << *ptr << endl;//智能指针可以像指针一样使用操作符*ptr = 30;//修改内存空间cout << *ptr << endl;//拷贝构造完成共享shared_ptr<int> ptr1(ptr);//此时ptr和ptr1共同管理一块内存cout << *ptr1 << endl;//30//辅助函数make_sharedshared_ptr<int> ptr2 = make_shared<int>(10);cout << *ptr2 << endl;//10shared_ptr<int> ptr3 = make_shared<int>();//管理一个没有初始化的int空间//查看引用计数cout << ptr.use_count() << endl;//2cout << ptr1.use_count() << endl;//2cout << ptr2.use_count() << endl;//1cout << ptr3.use_count() << endl;//1//赋值函数完成共享ptr3 = ptr2;//此时ptr3也管理了ptr2的这块内存cout << *ptr3 << endl;//10cout << ptr2.use_count() << endl;//2cout << ptr3.use_count() << endl;//2//重置ptr3.reset();//引用计数-1cout << ptr3.use_count() << endl;//0,ptr3被重置了,不再管理任何内存cout << ptr2.use_count() << endl;//1,ptr3离开了,只剩ptr2了ptr2.reset();cout << ptr2.use_count() << endl;//0,原来管理的内存空间被释放ptr1.reset(new int(5));//prt1被重置并且马上管理新内存cout << *ptr1 << endl;//5cout << ptr1.use_count() << endl;//1cout << ptr.use_count() << endl;//1//使用get方法返回裸指针shared_ptr<Test> ptr4(new Test(999));shared_ptr<Test> ptr5 = make_shared<Test>(888);Test* t1 = ptr4.get();t1->print();
}
共享指针的某些缺陷:
在释放有自定义类型组成的多个连续内存空间的时候,默认的析构函数无法完成释放的,这时候,需要我们自己手动指定释放的函数,这个函数称之为删除器。
//删除器的用法
void test03()
{shared_ptr<Test> ptr5(new Test(100), [](Test* t) {cout << "删除器来释放内存"<<endl; delete t; t = nullptr; });//手动指定删除器,使用匿名函数cout << ptr5.use_count() << endl;ptr5->print();//shared_ptr<Test> ptr_list(new Test[3]);//这个代码会报错,因为共享指针默认的删除函数无法完成连续多个的自定义内存空间的释放。shared_ptr<int> ptr_int_lis(new int[3]);//对于基本类型的连续空间,可以完成释放//接下来通过手写删除器完成自定义类型连续空间的释放,有三种写法://1)第一种写法,通过匿名函数自定义shared_ptr<Test> ptr6(new Test[3], [](Test* t) {delete[] t; t = nullptr; });cout << ptr6.use_count() << endl;//2)使用系统提供的删除器模板来完成释放shared_ptr<Test> ptr7(new Test[3], default_delete<Test[]>());//3)还是使用系统提供的删除器模板,但是可以简写:shared_ptr<Test[]> ptr8(new Test[3]);
}
共享指针使用的注意事项:
1)不能直接使用new来返回空间给智能指针,而应该使用构造函数或者辅助函数来完成初始化。
2)当我们使用一个普通指针来构造智能指针的时候,此时就不能再手动释放这个普通指针地址了,否则会造成多次释放,会报错。
3)不要用一个原始指针来初始化多个共享指针,这样也会造成多次释放的错误。
4)不要再函数参数中直接创建智能指针,而是应该在函数外创建好,再传参。因为不同的C++编译器再解释函数参数的顺序的时候是不同的。
比如:有个函数fun(shared_ptr<int>(new int(100)),参数2,参数3...),这样做是错的,应该先在外面完成智能指针的初始化,再传参:shared_ptr<int> p(new int(100)); fun(p,参数2,参数3...);
5)禁止在类里面返回一个管理当前对象指针(this)的智能指针。这样也会造成多次释放。
6)避免智能指针的循环引用问题。就是有两个类,互相含有管理对方类对象的智能指针。循环引用会造成引用计数出错,导致内存泄露。这个问题可以使用弱共享指针来解决。
7)智能指针放到容器中的时候,要注意,容器会单独拷贝一份,这个时候会导致引用计数+1,如果像正常释放内存的话,需要用完的时候再将容器清空。
//共享指针使用的注意事项:
class C
{int m_num;
public:C() { cout << "C的构造" << endl; }~C() { cout << "C的析构" << endl; }shared_ptr<C> getSharedPtr() { return shared_ptr<C>(this); }
};
//下面两个类A和B循环引用
class B;//提前声明B是个类
class A
{
public:shared_ptr<B> bsp;//这个智能指针可以管理一个B类对象~A() { cout << "A的析构" << endl; }
};
class B
{
public:shared_ptr<A> asp;//这个智能指针可以管理一个A类对象~B() { cout << "B的析构" << endl; }
};
void test04()
{shared_ptr<Test> ptest = new Test(2);//不能直接使用new来返回空间给智能指针应该使用构造函数来管理一个堆内存//shared_ptr<Test> pTest1(new Test(10));//shared_ptr<Test> pTest2 = make_shared<Test>(100);//Test* pTest = new Test(100);//普通指针//shared_ptr<Test> pShare(pTest);//交给智能指针管理//delete pTest;//这里的操作就不应该出现了,否则会出现多次释放的报错//pTest = nullptr;//int* p_int = new int(6);//shared_ptr<int> p_share_int1(p_int);//shared_ptr<int> p_share_int2(p_int);//cout << p_share_int1.use_count() << endl;//1//cout << p_share_int2.use_count() << endl;//1以上两个智能指针都是使用p_int指针完成的初始化,它们都在管理同一块内存。但是它们并没有共享引用计数。所以会导致多次释放,它们应该通过拷贝构造或者赋值函数来完成对一块内存的共享。//shared_ptr<C> c1(new C);//c1->getSharedPtr();//this指针指向了当前对象,它首先是被getSharedPtr函数释放了一次,然后又被外层的c1释放了一次。//循环引用问题shared_ptr<A> a(new A);//a管理了A对象cout << a.use_count() << endl;//1shared_ptr<B> b(new B);//b管理了B对象cout << b.use_count() << endl;//1//下面让a和b循环引用,通过赋值函数,让a的数据成员bsp跟b进行共享,让b的数据成员asp跟a进行共享a->bsp = b;b->asp = a;cout << a.use_count() << endl;//2cout << b.use_count() << endl;//2//可见引用计数变为2,这时候就不会调用析构函数释放内存,导致内存泄露。
}//结合容器来使用共享指针
//写一个函数,实现将一个Test对象交给智能指针来管理,并且把智能指针加入到vector中
void p_shared_vec(vector<shared_ptr<Test>>& vec, Test* t)
{shared_ptr<Test> p_Test(t);//先构造共享指针vec.push_back(p_Test);//再把共享指针放入容器中cout << "当前计数:" << p_Test.use_count() << endl;//2
}
vector<shared_ptr<Test>> vec;
void test05()
{Test* t1 = new Test(10);Test* t2 = new Test(20);Test* t3 = new Test(30);//调用函数p_shared_vec(vec, t1);p_shared_vec(vec, t2);p_shared_vec(vec, t3);for (auto& ele:vec){ele->print();//这个地方之所以还能打印出来,说明内存没有被释放,因为容器是一份单独的拷贝,容器中的智能指针还存在cout<<ele.use_count()<<endl;}//for (vector<shared_ptr<Test>>::iterator it = vec.begin(); it != vec.end() ; it++)//{// cout << it->use_count() << endl;//}vec.clear();//把容器清空,智能指针的计数才会变为0,此时才能释放内存
}
2)独占智能指针:
unique_ptr是一个独占型的智能指针,不允许跟其他智能指针共享内存。所以它禁用了拷贝构造函数和赋值函数。也禁用了引用计数。
独占指针的成员函数:
1)构造函数:unique_ptr<T> up(要管理的内存地址);
2)辅助函数:make_unique函数:unique_ptr<T> up=make_unique<T>();
3)重置函数:reset():直接重置,解除原原内存的管理,释放内存。 reset(要管理的新内存):重置之后马上管理新内存,原内存被释放;
4)get()函数:返回裸指针;
//2)独占智能指针
void test06()
{//构造函数和辅助函数unique_ptr<Test> uniqueP1(new Test(10));unique_ptr<Test> uniqueP2 = make_unique<Test>(8);uniqueP1->print();uniqueP2->print();Test* tp1 = uniqueP1.get();//get函数返回裸指针tp1->print();uniqueP1.reset();//重置uniqueP2.reset(new Test(100));//重置之后马上管理新地址
}
3)弱共享智能指针:
弱共享智能指针weak_ptr可以看作共享指针的助手,它不能管理内存,只是监视。所以它没有重载指针运算符*和->,它也不会增加引用计数。
弱共享智能指针的成员函数:
构造函数:weak_ptr<T> wp(监视的内存);
拷贝构造:weak_ptr<T> wp(另一个弱指针对象);
赋值函数:operator=();
use_count();查看引用计数,即代表有多少个智能指针在同时管理这块内存;
expired();判断监测的资源是否被释放;
lock();获取监测资源的共享智能指针对象;
reset();重置弱指针,不再监视任何内存;
//3)弱共享智能指针
void test07()
{shared_ptr<int> sp(new int(10));//第一个管理者cout << "sp_count:" << sp.use_count() << endl;//1weak_ptr<int> wp1;cout << "wp1_count:" << wp1.use_count() << endl;//0weak_ptr<int> wp2(sp);//此时wp2监视sp管理的内存cout << "wp2_count:" << wp2.use_count() << endl;//1weak_ptr<int> wp3(sp);//此时wp3也监视sp管理的内存cout << "wp3_count:" << wp3.use_count() << endl;//1//增加一个新的管理者shared_ptr<int> sp1 = sp;//这时候有两个管理者cout << "sp_count:" << sp.use_count() << endl;//2cout << "wp3_count:" << wp3.use_count() << endl;//2//接下来查看监视内存的状态cout << wp3.expired() << endl;//资源没有被释放,所以返回0wp3.reset();//wp3被重置了,不再监视任何内存cout << "wp3_count:" << wp3.use_count() << endl;//0cout << "wp2_count:" << wp2.use_count() << endl;//2shared_ptr<int> ptr = wp2.lock();//拿到某个共享指针对象cout << *ptr << endl;//10
}//通过弱共享指针可以解决循环引用问题
//只需要改变其中任何一个类,将共享指针改为弱共享指针即可打破循环引用,可以正常释放内存
class B_break;
class A_break
{
public:shared_ptr<B_break> bsp;~A_break() { cout << "A_break析构" << endl; }
};
class B_break
{
public:weak_ptr<A_break> asp;//这里换成弱共享指针,就可以打破循环引用。~B_break() { cout << "B_break析构" << endl; }
};
void test08()
{shared_ptr<A_break> a(new A_break);cout << a.use_count() << endl;//1shared_ptr<B_break> b(new B_break);cout << b.use_count() << endl;//1//通过赋值来循环引用a->bsp = b;//共同管理了B_break对象,此时引用计数还是2b->asp = a;//共同管理了A_break对象,但asp是弱指针,不会增加引用计数,所以引用计数还是1cout << a.use_count() << endl;//1cout << b.use_count() << endl;//2
}
总结:
1)共享指针用在所有权不明,多个指针管理同一个内存的情况。
2)独占指针强调的是专属所有权,只有他自己可以管理这块内存。
3)大家如果决定使用智能指针,就要保持一致都使用智能指针,而不要跟裸指针混用。
4)使用智能指针,要先做初始化,给每个智能指针起个名字。然后再使用。
5)牢记使用智能指针的注意事项。
6)智能指针虽然好用,但是会降低程序的性能,所以不适合用在高性能的场景。
