12 动态内存
12.1动态内存与智能指针
动态分配对象的生存期与它们在哪里创建无关,只有当显示地被释放时,这些对象才会销毁。
new在动态内存中为对象分配空间并返回一个指向该对象的指针,可以对对象进行初始化。
delete接受以一个动态内存指针,销毁该对象,并释放与之关联的内存。
12.1.1 shared_ptr类
shared_ptr<string> p1; //默认初始化的智能指针保存空指针
使用时判断是否为空
if(p1 && p1->empty())
*p1 = "hi";
make_shared函数
shared_ptr<int> p3 = make_shared<int>(42) //指向一个值为42的int的shared_ptr
auto p6 = make_shared<vector<string>>() //p6指向一个动态分配的空 vector<string>
shared_ptr的拷贝和赋值
每个shared_ptr都有一个关联计数器,称为引用计数:
计数器增加:
1、当拷贝一个shared_ptr时。
2、当用一个shared_ptr初始化另一个shared_ptr时
3、当shared_ptr作为参数传递给一个函数时,或作为函数返回时
计数器递减:
1、当给shared_ptr赋予一个新值
2、当shared_ptr被销毁(局部shared_ptr离开其作用域)
当shared_ptr计数器变为0,它会自动释放自己所管理的对象。
auto q = make_shared<int>();
auto r = make_shared<int>(42);
r = q; //q指向对象计数器加1,r原来指向对象引用计数器减1,计数器为0,r原来指向对象自动释放
shared_ptr自动销毁所管理的对象,并释放所关联的内存
shared_ptr类通过析构函数销毁所管理的对象。
12.1.2 直接管理内存
使用new动态分配与初始化对象
int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象
默认初始化,内置类型对象值未定义。类类型对象使用默认构造函数初始化
string *ps = new string; //初始化为空string
int *pi = new int; //pi指向一个未初始化的int
直接初始化:
int *pi = new int(1024);
构造方式(圆括号)
string *ps = new string(10,'9');
列表初始化()
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
值初始化
string *ps1 = new string();
由初始化器推断要分配对象类型:
auto p1 = new auto(obj); //p指向一个与obj类型相同的对象,该对象用obj进行初始化
动态分配const对象
const int *pci = new const int(1024);
内存耗尽
int *p1 = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int ; //如果分配失败,new返回一个空指针
释放动态内存
delete p; //p必须指向一个动态分配的对象或一个空指针
delete表达式执行两个动作:销毁给定指针指向的对象,释放对应的内存。
指针值和delete
不能释放一块非new分配的内存,或将相同的指针值释放多次。
new和delete管理动态内存的三个问题:
1、忘记delete内存。
2、使用已经释放掉的对象。
3、同一块内存释放两次。
空悬指针
delete一个指针后,指针无效,但该指针仍然保存着,这就是空悬指针。
在delete之后将nullptr赋予指针。
12.1.3 shared_ptr和new结合使用
使用new返回的指针来初始化智能指针
shared_ptr<int> p2(new int(42)); //p2指向一个值为42的int
不要混合使用普通指针和智能指针
void process(shared_ptr<int> ptr)
{
//使用ptr
//ptr离开作用域,被销毁
}
//正确
shared_ptr<int> p(new int(42));
process(p); //拷贝p会递增它的引用计数,在process中引用计数器为2
int i = *p;
//错误
int *x(new int(1024));
process(x); //不能减int*转换为一个shared_ptr<int>
process(shared_ptr<int>(x));//合法,当内存会被释放
int j = *x;//未定义,x是一个空悬指针
不要使用get初始化另一个智能指针或为智能指针赋值
shared_ptr<int> p(new int(42));
int *q = p.get();
{
shared_ptr<int>(q); //未定义,两个独立的shared_ptr指向相同的内存
} //程序结束,q被销毁,指向的内存被释放
int foo = *p //未定义:p指向内存已经释放了
其他shared_ptr操作
p = new int(1024); //错误,不能将一个新的指针赋予shared_ptr
p.reset(new int(1024));
12.1.4 智能指针和异常
当发生异常时,直接管理内存时不会自动释放的。智能指针指向的内存却可以释放。
void f()
{
shared_ptr<int> sp(new int(42));
//出现异常,未被捕获
} //函数结束时shared_ptr自动释放
void f()
{
int *IP= new int(42);
//出现异常,未被捕获
delete ip; //内存不会被释放
}
使用自己的释放操作
在shared_ptr中定义删除器。
12.1.5 unique_ptr
与shared_ptr不同,某时刻一个unique_ptr只能指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
初始化unique_ptr必须采用直接初始化的形式:
unique_ptr<int> p2(new int(42));
不支持普通拷贝和赋值
unique_ptr<string> p1(new string("abc"));
unique_ptr<string> p2(p1); //错误
unique_ptr<string> p3;
p3 = p2; //错误
通过release或reset将指针所有权从一个unique_ptr转移到另一个unique。
unique_ptr<string> p2(p1.release()) //release将p1置为空,p1放弃指针控制权,返回指针
unique_ptr<string> p3(new string("def"));
//将所有权从p3转移到p2
p2.reset(p3.release());//reset释放了p2原来指向的内存。
p2.release(); //错误 p2不会释放内存,而且会丢失指针
auto p = p2.release(); //正确,但必须记得delete(p)
传递unique_ptr参数和返回unique_ptr
可以拷贝或赋值一个将要被销毁的unique_ptr。
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
}
weak_ptr
weak_ptr指向一个shared_ptr管理的对象,将weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦指向对象的shared_ptr被销毁,对象就会被释放。
12.2 动态数组
12.2.1 new和数组
初始化动态分配对象的数组
auto p = new string[5]{"a","b","c","d","e"};
for(auto q = p;q < p+5; q++)
{
cout << *q << " ";
}
cout << endl;
delete [] p; //释放动态数组
智能指针和动态数组
unique<int[]> up(new int[10]); //unique<T[]> u(p) u指向内置指针p所指向的动态分配的数组。
up.release();
不能使用点和箭头成员运算符,只能使用下标运算符。
for(size_t i = 0; i !=10;++i)
up[i] = i;
使用shared_ptr管理动态数组
必须定义删除器。
12.2.2 allocator类
allocator
allocator<string> alloc; //可以分配string的allocator对象
auto const p = alloc.allocate(n); //分配n个未初始化的string
allocator分配未构造的内存
auto q = p;
alloc.construct(q++,10,'c');
销毁string
while(q != p)
alloc.destory(--q);//元素销毁后,可以重新使用这部分内存保存其他string
释放内存
alloc.deallocate(p,n);