12动态内部才能
12.1.1 shared_ptr
#include<memory>头文件
使用动态内存的原因是允许多个对象共享相同的状态。
负责自动释放所指向的对象,允许多个指针指向同一个对象。
shared_ptr<string>p1; //指向string的share_ptr指针
shared_ptr<list<int>>p2; //指向int的list的share_ptr指针。
一般的操作:shared_ptr和unique_ptr都支持
shared_ptr | 操作 |
---|---|
shared_ptr<T> sp | 空智能指针,可以指向类型为T的对象 |
p | 将p作为一个条件判断,若p指向一个对象,则为true |
*p | 解引用p,获得它指向的对象 |
p->men | 等价于(*p).men |
p.get() | 返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。 |
swap(p,q) | 交换p和q中的指针 |
p.swap(q) | 交换p和q中的指针 |
shared_ptr特有支持的操作
shared_ptr | 操作 |
---|---|
make_shared<T>(args) | 返回一个share_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。 |
shared_ptr<T>p(q) | p是shared_ptr q的拷贝:此操作会递增q中的计数器。q中的指针必须能转换为T*. |
p = q | p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。 |
p.unique() | 若p.use_count()为1,返回true,否则返回false. |
p.use_count() | 返回与p共享对象的智能指针数量;可能很慢,主要用于调试。 |
shared_ptr拷贝与赋值操作
当shared_ptr作为参数拷贝,初始化另一个shared_ptr,作为函数的返回值,所关联的计数器就会递增。
当我们给shared_ptr赋予一个新值,或shared_ptr被销毁,计数器就会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
shared_ptr<vector<int>> r = make_shared<vector<int>>();
auto r = make_shared<int>(42); //r指向的int只有一个引用者
r = q; //给r赋值,令它指向另一个地址; 递增q指向的对象的引用计数;递减r原来指向的对象的引用计数,r原来指向的对象已经没有引用者,会自动释放。
关键是智能指针类能记录有多少个shared_ptr指向相同的对象,并能在恰当的时候自动释放对象。
shared_ptr销毁对象
shred_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
void use_factory(T arg){
shared_ptr<Foo> p = factory(arg);
//使用p
//p离开了作用域,它指向的内存会被自动释放
}
shared_ptr<Foo> use_factory(T arg){
shared_ptr<Foo> p = factory(arg);
//使用p
return p; //当我们返回p时,引用计数进行了递增操作
//p离开的作用域,但它指向的内存不会被释放掉。
}
- [当你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。 ]
shared_ptr与new结合使用
shared_ptr<int> p2(new int(42)); //p2指向一个值为42的int
接受指针参数的智能指针构造函数是explicit的,所以不能进行内置指针到隐式指针的智能转换,必须使用直接初始化形式来初始化一个智能指针。 一个用来初始化智能指针的普通指针必须指向动态内存。
shared_ptr<int> p1 = new int(1024); //错误 必须使用直接初始化方式
shared_ptr<int> p2 (new int(1024)); //正确 使用了直接初始化方式。
shared_ptr<int> clone(int p){
return new int(p); //错误 隐式转换为shared_ptr<int>
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); //正确
}
使用智能指针的规范:
- 不使用相同的内置指针初始化(reset)多个指针。
- 不delete get()返回的指针
- 不使用get()初始化或reset另一个智能指针。
- 如果使用get()返回的指针,当最后一个对应的智能指针销毁后,你的指针就变为无效了。
- 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。
其他shared_ptr操作:使用reset来将一个新的指针赋予一个shared_ptr
p.reset(new int(1024)); //p指向一个新对象
与赋值类似,reset会更新计数,如果需要的话,会释放p指向的对象。
12.1.2 直接管理内存new and delete
#include<new> 头文件
new将内存分配与对象构造组合在了一起,delete将对象析构和内存释放组合在了一起。
int *p1 = new int ; //如果分配失败,new将抛出std::bad_alloc.
int *p2 = new (nothrow) int;//如果分配失败,new返回一个空指针。 定位new:允许我们向new传递额外的参数。
bad_alloc和nothrow都定义在头文件new中。
delete 释放动态内存
传给delete的指针必须是动态内存分配的,或是一个空指针,通常情况下,编译器无法判断指针指向的是静态还是动态对象。 delete也可用于const对象,虽然const对象的值不能被修改,但是其可以被销毁。 智能指针释放:最后一个shared_ptr被销毁 内置指针释放:由内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在。
void use_factory(T arg){
Foo *p = factory(arg);
//使用p
delete p ; //释放内存
}
new 和 delete管理内存常见问题
- 忘记delete内存。导致内部泄露问题,因为这种内存永远不可能被归还给自由空间了。查找内存泄露是非常困难的,通常应用程序运行很长时间后,真正耗尽内存后,才能检测到这种错误。
- 使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误。
- 同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了delete操作,对象的内存就被归还给自由空间了。如果我们随后又delete第二个指针,自由空间就会被破坏。
解决办法:坚持使用智能指针,就可避免所有这些问题。
12.1.3 make_shared
#include<memory>头文件
此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr;
shared_ptr<int> p1 = make_shared<int>(42); //指向一个值为42的int的shared_ptr;
shared_ptr<int> p2 = make_shared<string>(10,'9'); //指向一个值为“9999999999”的string.
shared_ptr<int> p5 = make_shared<int>(); //指向一个值初始化的int,即值为0;
通常用auto来保存make_shared的结果auto p6 = make_shared<vector<string>>(); //p6指向一个动态分配的空vector<string>
12.1.5 unique_ptr
#include<unique_ptr>头文件
- 某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
- 当我们初始化unique_ptr时,需要将其绑定到一个new返回的指针上,初始化unique_ptr必须采用直接初始化的方式。
-unique_ptr不支持普通的拷贝或赋值操作。
一般的操作:shared_ptr和unique_ptr都支持
shared_ptr | 操作 |
---|---|
shared_ptr<T> sp | 空智能指针,可以指向类型为T的对象 |
p | 将p作为一个条件判断,若p指向一个对象,则为true |
*p | 解引用p,获得它指向的对象 |
p->men | 等价于(*p).men |
p.get() | 返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。 |
swap(p,q) | 交换p和q中的指针 |
p.swap(q) | 交换p和q中的指针 |
unique_ptr特有支持的操作
unique_ptr | 操作 |
---|---|
unique_ptr<T> u1 | 空unique_ptr,可以指向类型为T的对象。u1使用delete来释放它的指针。 |
unique_ptr<T,D> u2 | u2使用一个类型为D的可调用对象来释放它的指针 |
unique_ptr<T,D> u(d) | 空unique_ptr,指向类型为T的对象,用类型为D的对象d来代替delete. |
u = nullptr | 释放u指向的对象,将u置为空 |
u.release() | u放弃对指针的控制权,返回指针,并将u置为空 |
u.reset() | 释放u指向的对象 |
u.reset(q) | 如果提供了内置指针q,令u指向这个对象;否则将u置为空。 |
u.reset(nullptr) | 否则将这个对象置为空 |
调用release或reset来转移指针的所有权
unique_ptr<string> p2(p1.resease()); //release将p1置为空 将所有权从p1转移给p2
p2.reset(p3.release()); //reset释放了p2原来指向的内存
调用realse会切断unique_pre与原来所管理对象之间的关系。
向imoqie_ptr传递删除器
unique_ptr<objT,delT> p (new objT,fcn);// p指向一个类型为objT的对象,并且使用一个类型deLT的对象来释放objT对象。
//它会调用一个名为fcn的delT类型对象。
12.1.6 weak_ptr
#include<memory> //头文件
- weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的计数。
- 一旦最后一个指向对象shared_ptr被销毁,对象就会释放。
weak_ptr | 操作 |
---|---|
weak_ptr<T> w | 空weak_ptr可以指向类型为T的对象 |
weak_ptr<T> w(sp) | 与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型。 |
w = p | p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象 |
w.reset() | 将w置为空 |
w.ues_count() | 与w共享对象的shared_ptr的数量 |
w.expired() | 若w.use_count()为0,返回true否则返回false |
w.lock() | 如果expired()为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr. |
创建一个weak_ptr
创建一个weak_ptr,需要用shared_ptr来初始化它。
auto p = make_shared<int>(42);
weak_ptr<int>wp(p); //wp弱共享p;p的引用计数未改变。
weak_ptr通过lock函数访问对象
lock函数检查weak_ptr指向的对象是否存在,如果存在,则lock返回一个指向共享对象的shared_ptr。
if(shared_ptr<int> np = wp.lock()){ //如果np不为空,则条件成立
//在if中,np与p共对象
}
12.2.2 allocator 类
#include<memory> //头文件
当分配一大块内存时,我们通常计划在这块内存上按需构造对象,在此情况下,我们希望将内存分配和对象构造分离,这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象创建操作。
- 将内存分配与对象构造分离开来
- 提供一种类型感知的内存分配方法
- 分配的内存是原始的、未构造的
- 必须指明allocator可以分配对象的类型
- 它自动根据对象的类型来确定恰当的内存大小和对齐位置
allocator<string>alloc; //可以分配string的allocator对象
auto const p = alloc.allocate(n); //分配n个未初始化的string.
allocator支持的操作
allocator | 操作 |
---|---|
allocator<T> a | 定义一个名为a的allocator对象 |
a.allocator(n) | 分配内存,保存n个类型为T的对象 |
a.deallocate(p,n) | 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy |
a.construct(p,args) | p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。 |
a.destroy(p) | p为T*类型的指针,此算法对p指向的对象执行析构函数 |
allocator分配未构造的内存
- 使用construct来构造 construct成员函数接受一个指针和零个或多个额外的参数,在给定位置构造一个元素,额外参数用来初始化构造的对象,类似于make_shared的参数,这些额外参数必须是与构造的对象的类型相匹配的合法的初始化器。
auto p = q; //q指向最后构造的元素之后的位置
alloc.construct(q++); //q为空字符串
alloc.construct(q++,10,'c'); //*q为ccccccccc
alloc.construct(q++,"hi"); //*q为hi!
- 使用destroy来销毁 函数destroy接受一个指针,对指向的对象执行析构函数
while(q != p)
alloc.destroy(--q);//释放我们真正构造的string
我们只能对真正构造了的元素进行destroy操作
- 使用deallocate来释放这片内存
alloc.deallocate(p,n);
传递给deallocate的指针不能为空,它必须指向由allocae分配的内存。 传递给deallocate的大小参数必须与调用allocated分配内存时提供的大小参数具有一样的值。
拷贝或填充未初始化内存的算法
标准库还为allocator定义了里两个伴随算法,可以在未初始化内存中创建对象。这些函数在给定目的位置创建对象,而不是由系统分配内存给他们。
uninitialized_copy(b,e,b2) //从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2执行的内存必须足够大,能容纳输入序列中元素的拷贝。
uninitialized_cop_n(b,n,b2) //从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。
uninitialized_fill(b,e,t) //在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝。
uninitialized_fill_n(b,n,t) // 从迭代器b指向的内存地址开始创建n个对象,b必须指向足够大的未构造的院士内存,能够容纳给定数量的对象。
//假定有一个int的vector,希望将其内容拷贝到动态内存中,我们将分配一个比vector中元素所占用空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,对后一半空间用一个给定值进行填充。
auto p = alloc.allocate(vi.size()*2);//
//通过拷贝vi中的元素来构造从p开始的元素
auto q = uninitialized_copy(vi.begin(),vi.end(),p);
//将剩余元素初始化为42
uninitialized_fill_n(q,vi.size().42);
与copy不同,uninitialized_copy在给定目的位置构造元素。 一次uninitialzed_copy调用会返回一个指针,指向最后一个构造的元素之后的位置。