为了一定程度解决手动分配的动态内存忘记释放的问题,C++引入了智能指针的概念。并且在C++11中,将智能指针进行了优化,更加方便易用。
智能指针,顾名思义,实质还是指针,不过它很智能,会在合适的时机自己释放内存,无需手动delete。其原理就是引入计数,一旦指向某一块内存的指针数为0,则释放。
常用的智能指针为共享指针shared_ptr和独占指针unique_ptr,共享指针可以多个智能指针指向同一块内存;而独占指针则在某个时刻只能有一个智能指针指向某一块内存。
用智能指针,需要包含头文件:#include <memory>。
这篇文章仅介绍共享智能指针shared_ptr的使用及注意事项,另一篇文章介绍独占智能指针unique_ptr的知识。
1、创建及初始化
共享指针的创建,最安全的做法是使用标准库的std::make_shared函数,具体语法如下:
std::shared_ptr<类型>(参数列表)
智能指针是一个模板。
其中,类型即需要创建的智能指针类型,比如int、double或者自定义的类;参数列表可以理解成类的构造函数需要的参数,可以为空,但小括号必须要。
例如,自定义Person类,如下:
class Person
{
public:
Person(const std::string& strName, int iAge) :m_strName(strName), m_iAge(iAge){}
~Person(){ std::cout << "Person析构, name=" << m_strName << std::endl; }
void PrintInfo(){ std::cout << "姓名:" << m_strName << ", 年龄:" << m_iAge << std::endl; }
private:
std::string m_strName;
int m_iAge;
};
在主函数中创建一个Person类以及int类型的shared_ptr并使用,代码如下:
int _tmain(int argc, _TCHAR* argv[])
{
auto p2 = std::make_shared<int>(25); //初始化为25,并且用auto来定义对象
std::cout << "pInt2:" << *p2 << std::endl;
std::shared_ptr<int> pInt1; //定义后并未立即初始化
pInt1 = std::make_shared<int>(); //这里参数列表为空,int会默认初始化为0
std::cout << "pInt1:" << *pInt1 << std::endl;
//自定义类
std::shared_ptr<Person> pZhangFei = std::make_shared<Person>("张飞", 42);
pZhangFei->PrintInfo();
system("pause");
return 0;
}
执行结果如下:
2、拷贝、赋值及自动释放内存
添加一个根据名字和年龄,获取相应智能指针的接口,如下:
std::shared_ptr<Person> GetPerson(const std::string strName, int iAge)
{
return std::make_shared<Person>(strName, iAge);
}
主函数修改如下:
int _tmain(int argc, _TCHAR* argv[])
{
{
std::shared_ptr<Person> p1 = GetPerson("赵云", 40);
{
std::shared_ptr<Person> p2 = GetPerson("关羽", 48);
p2->PrintInfo();
p1 = p2; //此时原p1指向的内存对象会被释放掉,p1与p2指向同一个对象
}
p1->PrintInfo();
}
system("pause");
return 0;
}
执行结果:
主函数中,p1初始化的是赵云,接下来一对大括号,p2被初始化为关羽,打印p2,得到的是关羽的信息;接下来,将p2赋值给p1,此时,p1原本指向的内存对象赵云,没有任何指针指向它,计数变为了0,所以会自动释放其内存,调用析构函数,打印释放赵云对象的信息。赋值结束后,p2的作用域结束,但是因为p1已经指向了对象关羽,所以关于对象的指针计数不为0,内存并不会被释放;打印p1的信息,依然是关羽。接下来遇到大括号,p1的作用域失效,指向关羽的指针计数变成0,内存会被自动释放,打印了释放对象关羽的信息。
shared_ptr指向的内存对象,只有当指向它的智能指针引用计数为0时,才会自动释放。
理解这几行代码,基本上就可以理解共享指针自动释放内存的原理。
3、注意事项
(1)不要混用普通指针和智能指针
谨记一点,不要将普通指针赋值给智能指针,否则可能会引发很严重的后果。
请看下面代码:
void Process(std::shared_ptr<int> p){}
int _tmain(int argc, _TCHAR* argv[])
{
int* p1 = new int(20);
std::cout << *p1 << std::endl;
Process(std::shared_ptr<int>(p1));
if (p1)
std::cout << *p1 << std::endl;
system("pause");
return 0;
}
代码很简单,定义了一个什么都不做的Process函数,接收一个shared_ptr<int>类型的智能指针作为参数。
主函数里,new一个普通指针p1,打印p1指向的对象内容;再调用Process函数并以p1作为参数构建一个临时的智能指针作为参数传递,再打印p1。
结果会是什么呢?
有些出乎意料,为何会如此?
第一次打印出来20,很简单,就是p1指向的内存内容。第二次打印的是未定义的内容,说明p1成了一个空悬指针。
问题出在Process的调用上。我们先用p1作为参数,构建了一个临时的share_ptr指针,这没问题;但是这个临时的智能指针指向的是p1的内存对象,当Process执行结束,这个临时的智能指针作用域消失,其指向的内存引用计数变成0,会自动释放内存,即p1指向的内存。此时p1依然有效,但是它的内存已经被释放,p1就成了一个空悬指针(注意,用if(p1)来判断p1仍然是有效的)。
(2)慎用get
可用get获取智能指针中的指针,但一定要注意,千万不要误delete这个普通指针。
看如下代码:
int _tmain(int argc, _TCHAR* argv[])
{
std::shared_ptr<int> pShare(new int(7));
std::cout << *pShare << std::endl;
int *p = pShare.get();
delete p;
if (pShare)
std::cout << *pShare << std::endl;
system("pause");
return 0;
}
执行结果如下:
这里用new 的方式初始化了一个shared_ptr指针,打印内容为7,没问题。
接着,调用shared_ptr的接口get,获取其内部的指针,赋值给普通指针p;紧接着,delete p,将普通指针释放。此时,pShare的指向的内存也被释放了,用if判断,pShare本身依然有效;尝试打印其中内容,未定义。
共享指针的使用比较简单,就介绍这么多。