在C++中我们为了避免内存泄漏而引入的智能指针
因为在C++中我们频繁使用堆内存,但是堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。我们使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题,使用智能指针能更好的管理堆内存。实际上我们这个智能指针即对象,因为对象在程序结束时会自动调用其析构,就不会发生内存泄漏了,这也是我们实现智能指针一个重要的基础思想。
对智能指针的描述
智能指针是对普通指针用面向对象的类进行的一个封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
智能指针的智能就在于它能够在适当的时机安全的帮助你释放内存
智能指针主要是运用了RAII技术(也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象)
1:auto_ptr
auto_ptr是C++98标准化引入的,auto_ptr十分简单,但使用起来却到处是坑,所以不提倡大家使用,C++11已放弃该关键字。低版本的VS并没有将其加入标准库(boost)。
在auto_ptr中构造函数是浅拷贝,将原有资源转给最新的对象,将自己置为空。auto_ptr的赋值也是一样,会将自身置空,将最新的指针指向原有空间,所以auto_ptr是不允许赋值的,我们程序员要注意这点。
在容器中我们要注意不要使用auto_ptr,因为容器操作涉及很多拷贝构造,不允许浅拷贝
模拟代码如下:
typedef struct TestAutoPtr //一个作为测试的结构体而已。。。
{
TestAutoPtr(int val=int()):mval(val){}
int mval;
}tap;
template<typename T>
class AutoPtr
{
public:
//构造函数
AutoPtr(T *a=NULL):ptr(a){}
//拷贝构造,转交权限,所以参数不加const
AutoPtr(AutoPtr &rt):ptr(rt.ptr)
{
rt.ptr=NULL;
}
//赋值运算符重载,因为移交了管理权,所以auto_ptr是不允许赋值的
AutoPtr& operator=(AutoPtr &rt)
{
if(&rt!=this)
{
if(ptr!=NULL) //这个判断不要也行,delete NULL不会出错
{
delete ptr;
ptr=NULL;
}
ptr=rt.ptr;
rt.ptr=NULL; //权限转移,自身赋为空
}
return *this;
}
//重载*运算符
T &operator *()
{
return *ptr;
}
//重载->运算符
T *operator ->()
{
return ptr;
}
//析构函数
~AutoPtr()
{
if(ptr!=NULL)
{
delete ptr;
ptr=NULL;
}
}
private:
T *ptr;
};
测试用例:
int main()
{
AutoPtr<int> a1(new int(2));
AutoPtr<int> a2=a1; //此时a1已指向空
//cout<<*a1<<endl; //a1指向空,解引用就崩了
int *p=new int(5);
AutoPtr<int> a3(p);
a2=a3;
cout<<*a2<<endl; //5
//cout<<*a3<<endl; //a3指向空,解引用就崩了
AutoPtr<tap> a4(new tap(8));
cout<<a4->mval<<endl; //相当于a4.operator->()->mval
/*
int *q=new int(6);
AutoPtr<int> a5(q); //因为a5与a6指向同一块空间,析构的时候就会将那块空间释放两次,会发生错误
AutoPtr<int> a6(q);
cout<<*a5<<endl; //6
cout<<*a6<<endl; //6
*/
}
2:scope_ptr
它是boost库中的,scope_ptr的用法与auto_ptr几乎一样,大多数情况下它可以与auto_ptr相互替换,也可以从一个auto_ptr获得指针的管理权(同时auto_ptr失去管理权)。scope_ptr与auto_ptr的根本区别在于指针的所有权。auto_ptr特意被设计为指针的所有权是可转移的,可以在函数之间传递,但是同一个时刻只能有一个auto_ptr管理指针,scope_ptr把拷贝构造函数和赋值函数都声明为私有的,拒绝了指针的所有权转让,任何人都无权访问被管理的指针,从而保证了指针的绝对安全。
概括来说就是:scope_ptr不允许拷贝,赋值,只能在scope_ptr被声明的作用域内使用,除了*和->外scope_ptr也没有定义其他的操作符(不能对scope_ptr进行++或者--等指针算术操作),这样就不用考虑浅拷贝的问题了。
同样,因为其不能进行拷贝和赋值,所以不能用作容器的元素
3:unique_ptr
来源于boost库,实际上unique_ptr与auto_ptr很像,都是让最后一个指针指向资源,将之前的指针置为空。但是unique_ptr与auto_ptr还是有些不同,且看下文。
无法直接拷贝构造和赋值
unique_ptr<int> s1(new int(5));
unique_ptr<int> s2(s1); //不允许拷贝构造,编译器报错
unique_ptr<int> s3(new int(6));
s1= s3; //不允许赋值,编译器报错
可以进行移动赋值和移动构造(间接)
因为它没有办法直接进行拷贝构造和赋值,那么我们如何转移管理权呢?
unique_ptr<int> GetVal() //作为返回值是可以进行拷贝的,注意不能返回&,要生成临时对象,否则出错
{
unique_ptr<int> p(new int(8));
return p;
}
int main()
{
unique_ptr<int> s4 = GetVal(); //成功进行了拷贝
}
实际上,上面的转移管理的代码我们还可以这样写:
int main()
{
unique_ptr<int> s4(new int(5));
unique_ptr<int> s5 = std::move(s4); //std::move将s4的内存转移给了s5,s4就被指向了空,编译器不会报错,std::move时C++11出现的
cout<<*s5<<endl; //5
//cout<<*S4<<endl; //程序崩溃
}
可以作为容器元素
我们知道auto_ptr和scope_ptr都不可做为容器元素,而unique_ptr也同样不能直接做为容器元素,但可以通过间接作为容器元素
int main()
{
unique_ptr<int> s6(new int(10));
vector<unique_ptr<int>> vec;
/*
编译不通过,不能直接插入s6,
push_back(s6);
*/
vec.push_back(std::move(s6)); //成功插入
cout << *s6 << endl;//这个出错了,说明添加到vec容器中,s6本身就作废了
}