智能指针是行为类似于指针的类对象,但这种对象还有其他功能。使用指针指向一块新申请的内存的过程中,有时忘记释放新申请的内存,导致内存泄漏。为了防止该问题的发生,C++提供了智能指针模板类。其思想就是将常规的指针变成一个类对象,该对象主要实现常规指针的功能,当该对象过期的时候,会自动调用其析构函数,在析构函数中完成内存释放的操作。
智能指针有auto_ptr、unique_ptr、shared_ptr和weak_ptr(本章暂时不做详细介绍),其中auto_ptr在C++11中摒弃。
class A{...};
void fun()
{
A* pa = new A;
std::auto_ptr<A> p1 = new A;
std::unique_ptr<A> p2 = new A;
std::shared_ptr<A> p3 = new A;
...
delete pa; //必须在pa过期之前释放分配的内存
//delete p1;//不需要手动释放内存,在p1过期的时候,会自动调用其析构函数释放对应的内存块
//delete p2;//不需要手动释放内存,在p2过期的时候,会自动调用其析构函数释放对应的内存块
//delete p3;//不需要手动释放内存,在p3过期的时候,会自动调用其析构函数释放对应的内存块
}
常规的指针在使用的过程中两个指针可能会指向同一个对象,这是不能接受的,因为程序在结束的时候会存在删除同一对象两次的问题,如下例:
std::string* ps (new std::string("abc"));
std::string* p1;
p1 = pa;
...
delete ps;
delete pa;
针对上述问题,auto_ptr和unique_ptr在常规指针的基础上增加了所有权的概念,对于特定对象,只有一个指针可以拥有它,这样只有拥有对象的智能指针才能删除该对象。
而shared_ptr采用跟踪引用特定对象的智能计数(即引用计数)策略。例如,赋值的时候,计数加1,而指针过期的时候,计数减1,仅当最后一个指针过期的时候才调用delete。
下面我们举个例子,看一下他们之间的区别。
auto_ptr<std::string> p1(new std::string("abc"));
auto_ptr<std::string> p2;
p2 = p1; //p2接管string对象的所有权后,p1的所有权被剥夺,其不再指向有效数据,后面如果尝试调用p1,程序运行的时候则会导致程序core dumped
unique_ptr<std::string> p1(new std::string("abc"));
unique_ptr<std::string> p2;
p2 = p1; //p2接管string对象的所有权后,p1的所有权被剥夺,其不再指向有效数据,后面如果尝试调用p1,则编译失败
shared_ptr<std::string> p1(new std::string("abc"));//引用计数器为1
shared_ptr<std::string> p2;
p2 = p1; //引用计数器为2
//在p1、p2过期的过程中,计数器相应减1,当最后一个智能指针过期的时候,调用修购函数时,将引用计数器降低到0,并释放对应的空间
从上面的例子可以看出unique_ptr与auto_ptr最大的区别就在于当一个智能指针的所有权被剥夺后,若后面的程序要调用它的时候,unique_ptr直接在编译阶段就失败,将问题暴露出来,而auto_ptr编译阶段不会报错,在程序运行的时候出现异常,因此unique_ptr的安全性更高(编译阶段错误比程序崩溃更安全)。
相比auto_ptr,unique_ptr另外一个优点就是它有一个可以用于数组的变体。我们知道delete与new配对,delete[]与new[]配对,而auto_ptr中使用的是delete,而不是delete[],因此只能与new一起使用,不能与new[]一起使用。但unique_ptr有delete和delete[]两个版本,它可以与new一起使用,也可以与new[]一起使用。同样的,shared_ptr也支持与new一起使用,不支持new[]的使用。
注:使用new分配内存时,可以使用auto_ptr、unique_ptr、shared_ptr;使用new[]分配内存时,不能使用auto_ptr和shared_ptr,可以使用unique_ptr;不使用new和new[]分配内存时,不能使用unique_ptr。
shared_ptr。unique_ptr。