目录
12.1 动态内存与智能指针
c++中,通过new运算符和delete来管理动态内存,新标准中提供了两种智能指针,智能指针与普通指针的区别在于,它负责自动释放所指向的对象,shared_ptr允许多个指针指向同一对象,unique_ptr则独占所指向的对象。该指针定义在memory头文件中
12.1.1 shared_ptr类
(1)定义:同vector一样,定义时候需要指出指针所指向的对象
shared_ptr<string> p1;
(2) make_shared函数:
此函数在动态内存分配中分配一个对象并且初始化它,返回指向此对象的shared_ptr。当要使用该函数时,必须指定想要创建的对象的类型。
shared_ptr<int> p3=make_shared<int>(42) //指向一个值为42的int的shared_ptr;
(3)shared_ptr的拷贝和赋值
进行拷贝或赋值时,每个shared_ptr会记录有多少个其他shared_ptr指向相同的对象,其内部存在一个引用计数,当用一个shared_ptr去初始化另一个shared_ptr时,其关联的计数会递增,当计数器为0时,通过析构函数自动销毁其所管理的对象。
auto p=make_shared<int>(42);//p指向的对象只有p一个引用者
auto q(p)//p和q指向相同对象,此对象有两个引用者,递增q所指向对象的引用计数
我们认为每个shared_ptr都有一个关联的计数器,通常称为引用计数,当我们拷贝一个shared_ptr时,计数器会递增。
一旦一个shared_ptr的计数器为0,他就会自动释放所指向的对象。
12.1.2 直接管理动态内存
(1)使用new动态分配内存和初始化对象
int *pi=new int;
自由空间分配的内存是无名且默认初始化的,返回指向该对象的指针,可以直接初始化的方式来初始化一个动态内存对象
int *pi=new int(42)//pi指向对象的值为42,若()内为空,则默认初始化为0
const int *pci=new int(12)//合法,且动态分配的const对象必须初始化,返回一个指向const的指针
(2)delete
delete表达式接受一个指针,释放所指向的对象。传递给delete的指针必须指向动态分配的内存,或者是一个空指针,释放一块非new分配的内存或者将相同的指针值释放多次,其行为是未定义的。
delete一个指针后,指针值变为无效,变为空悬指针,即指向一块曾经保存数据对象但现在已经无效的内存的指针。可将nullptr赋与该指针避免空悬指针问题。
(3)动态分配的const对象
用new分配const对象是合法的:
const int *p=new const int (1024);
一个动态分配的const对象必须进行初始化
(4)new若无法分配所要求的空间,会抛出一个类型为bad_alloc的异常
12.1.3 shared_ptr和new结合使用
若不初始化一个智能指,其就会被初始化为一个空指针,可以使用new返回的指针来初始化智能指针,使用方法:shared_ptr<int> p2(new int(42))//p2指向一个值为42的int
必须将shared_ptr显示绑定到一个想要返回的指针上
shared_ptr<int> clone(int p)
{
return new int (p);//错误
}
shared_ptr<int> clone(int p)
{
return shared_ptr<int>(new int(p));//正确
}
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放对象。
(1)不要混合使用智能指针和普通指针
void process(shared_ptr<int> ptr)
{
//使用ptr
}//ptr离开作用域
传值方式传递给ptr,拷贝一个shared_ptr会递增其引用计数,因此,上述函数运行时,引用计数至少为2,当process结束时,ptr的引用计数会递减,但不会变为0,因此,当局部变量ptr被销毁时,其指向的内存不会被销毁
shared_ptr<int> p(new int(1024))
process(p);
int i=*p//正确,引用计数值为1
int *x(new int(1024)) //x为一个普通指针
process(x) //错误,x不能转化为shared_ptr
process(shared_ptr<int>(x)); //合法,但内存会被释放
int j=*x //错误,x为空悬指针
12.1.4 智能指针和异常
void f()
{
shared_ptr<int> sp(new int(42));
//这段代码抛出一个异常,且在f中被捕获,sp也会被销毁
}//函数结束时sp也会被销毁
void f()
{
int *ip=new int(42);
//代码捕获异常,则ip不会被释放
delete ip;
}//若函数能正常结束,ip才会被释放
12.1.5 unique_ptr
定义:某个时刻只能指向一个给定的对象,被销毁时,所指定的对象也会被销毁
定义一个unique_ptr时,必须将其绑定到一个new 返回的指针上, 初始化时必须采用直接初始化形式
unique_ptr<int> p2(new int(42));
不支持拷贝和赋值操作,但是有一种情况例外,可以拷贝一个将要被销毁的unique_ptr,常见例子为函数返回。传递unique_ptr参数和返回unique_ptr
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));//正确,从int*创建一个unique_ptr
}
12.1.6 weak_ptr
(1)定义:weak_ptr是一种不控制所指向对象生存周期的智能指针,指向一个shared_ptr管理的对象,将weak_ptr绑定到shared_ptr不会改变shared_ptr引用计数,一旦最后一个指向对象的shared_ptr被释放,则weak_ptr也会被释放。
创建一个weak_ptr时需要一个shared_ptr来初始化他。
auto p=make_shared<int>(42);
weak_ptr<int> wp(p);
对象可能不存在,所以不能通过weak_ptr直接访问对象,必须调用lock。此函数检查weak_ptr指向对象是否存在,如果存在,返回一个指向共享对象的shared_ptr。
if(shared_ptr<int> np=wp.lock()) //如果np不为空则条件成立,即lock返回true
{
}
12.2 动态数组
12.2.1 new和数组
int *p=new int[size] //new 返回的是一个元素类型的指针
int *pi=new int[size]() //默认初始化,值均为0
int *p=new int[2]{1,2};
删除:
delete [] p;
(1)分配空数组是合法的
size n=get_size();
int *p=new int[n];
for(int *q=p;q!=p+n;++q)
若n为0,上述定义也是合法的,new分配一个大小为0的数组,返回一个合法的非空指针,此指针就像尾后指针一样。可以像使用尾后迭代器一样使用此指针。但此指针不能解引用-因为它不指向任何元素。上述for循环会失败,因为p=q+n,因为n为0,因此循环不会被执行。
(2)使用unique_ptr管理动态数组
unique_ptr<int []> up (new int[10]);//up指向一个int数组
up.release();//自动调用delete[]销毁指针
可以使用下标运算符访问数组中的元素
for(int i=0;i<10;i++)
up[i]=i;
shared_ptr不支持管理动态数组,如果想要使用,必须定义自己的删除器。
12.2.2 allocator类
该类定义在头文件memory中,将内存分配和对象构造分离开来,当一个allocator对象分配内存时,他会根据给定的对象类型和确定恰当的内存大小和对其位置。
(1)定义:
allocator<string> alloc; //可以分配string的allocator对象
auto const p=alloc.allocate(n); //分配n个未初始化的string
(2)allocator分配未构造的内存
allocator分配的内存是未构造的,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素,额外参数用来初始化构造的对象.
alloc.construct(p,"hi")
使用完对象后,必须对每个构造的元素调用destroy来销毁。函数destroy接收一个指针,对指向的对象执行析构函数
alloc.destroy(q); //释放真正构造的string
alloc.deallocate(p,n); //释放内存
例:将vector中的元素拷贝过来,分配一个大两倍的空间,后面的用以定值填充。
vector<int> v = { 1,2,3,4,5,6 };
allocator<int> alloc;
auto p = alloc.allocate(v.size() * 2);
auto q = uninitialized_copy(v.begin(), v.end(), p);
uninitialized_fill_n(q, v.size(), 10);