Loading

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被销毁,计数器就会递减。
一旦一个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调用会返回一个指针,指向最后一个构造的元素之后的位置。

posted @ 2019-11-15 16:41  三只猫-  阅读(355)  评论(0编辑  收藏  举报