C++ Primer第十二章③
C++ Primer
更多内容戳这里
动态内存
shared_ptr和new结合使用
这个标题是我们的目标,同时我们也要想,为什么要结合着使用。在此之前,先看些基础知识。
智能指针的构造函数是explicit的
可能你已经忘了explicit是什么意思了,我回去翻了下,是禁止隐式转换的意思,就因为这个规定,接下来就有一些事情可以搞了:
shared_ptr p1(new int(24)); //我们知道这样是对的,直接初始化
shared_ptr p2 = new int(24); //那这样呢?错了
第二个为什么错了呢?因为等号右边用new构造临时对象时返回的是一个普通指针int*,它试图隐式转换为智能指针,但是我们的智能指针的构造函数很无情,不允许隐式转换,所以报错了。 再来个函数返回类型的例子:
shared_ptr clone(int p)
{
return new int(p); //错误:试图隐式转换为shared_ptr
}
//正确示例
shared_ptr clone(int p)
{
return shared_ptr( new int(p) );
}
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存(就是说它得指向new出来的),因为智能指针会delete啊。我们当然也可以让智能指针指向其他类型的资源,但是你得提供自己的释放操作去代替delete。(以后再介绍怎么定义自己的释放操作。)
下面两个图看表格就行
不要混合使用普通指针和智能指针
来个错误例子:
void process(shared_ptr ptr){}
int *x(new int(24));
process(shared_ptr(x));
int j = *x; //x是空悬指针,自己想想为啥,这段代码很重要啊,好好看看
也不要使用get初始化另一个智能指针或为智能指针赋值
还是来一个会导致空悬指针的示例:
shared_ptr p(new int(42));
int *q = p.get();
{
shared_ptr(q); //两个独立的智能指针指向相同的内存块(要出事情)
} //q指向的内存被销毁
int foo = *p; //p是空悬指针,报错。
其他shared_ptr操作
shared_ptr p(new int(42));
p = new int(1024); //错误:禁止隐式转换
p.reset(new int (1024)); //这样可以,p指向一个新对象
//有时为了避免p原来的内存被释放,我们还会这么写
if(!p.unique()){ p.reset(new string (*p)); }
智能指针和异常
使用智能指针的好处:即便代码出现没有catch到的异常,还是能保证内存的释放:
void f()
{
shared_ptr sp(new int(42));
//假设之后的代码出现了没有捕捉到的异常
} //在函数结束时,shared_ptr还是会自动释放内存
//假设代码中还有局部变量来拷贝sp呢,还是会正确释放,除非有函数外部的变量拷贝了sp,想想这是为啥
普通指针来管理动态内存的坏处:
void f()
{
int* p = new int(42);
//假设之后的代码出现了没有捕捉到的异常
delete ip;
}
//虽然代码很好地写了delete ip,但出现异常之后p指向的内存就不会被释放了,根本无法释放
一些没有定义析构函数的类也会出现类似问题:
void f()
{
connect c; //默认初始化
} //如果没有显示销毁c,那c就不会被销毁了
我们如何改进呢,用智能指针管理,并且定义一个函数(删除器)用来代替delete函数:
void end_connect(connect *p){ disconnetc(*p); } //定义了删除器(调用了一个函数)
void f()
{
connect c;
shared_ptr p(&c, end_connect); //用p来管理c的内存
}//退出后c就会被正确关闭销毁