C++智能指针

来源:互联网 发布:淘宝主图背景图片大全 编辑:程序博客网 时间:2024/06/05 06:08

直接管理内存

什么时候需要直接管理

简而言之,当内存分配在栈上时,不需要直接管理,而当内存分配在堆上时则需要手动回收,或者等到堆上内存分配满了触发了自动回收机制。 
关于堆和栈,这篇文章讲得浅显易懂:http://blog.csdn.net/hairetz/article/details/4141043 
一个由C/C++编译的程序占用的内存分为以下几个部分

  1. 栈区(stack)—— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区(heap)—— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
  3. 全局区(静态区)(static)——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
  4. 文字常量区——常量字符串就是放在这里的,程序结束后由系统释放 。
  5. 程序代码区——存放函数体的二进制代码。

例子程序

这是一个前辈写的,非常详细

//main.cpp    int   a   =   0;   全局初始化区    char   *p1;   全局未初始化区    main() { int   b;   栈    char   s[]   =   "abc";   栈    char   *p2;   栈    char   *p3   =   "123456";   123456/0在常量区,p3在栈上。    static   int   c   =0;   全局(静态)初始化区    p1   =   (char   *)malloc(10);    p2   =   (char   *)malloc(20);    分配得来得10和20字节的区域就在堆区。    strcpy(p1,   "123456");   123456/0放在常量区,编译器可能会将它与p3所指向的"123456" 优化成一个地方。    }

注意,除了上文的malloc,new分配的内存也在堆中需要手动销毁。

动态内存

由上文看出,分配在堆上的内存需要手动进行动态分配和释放,我们将之称为动态内存。C++中,动态内存是通过new和delete来进行分配和释放的。 
new:在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化。 
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。 
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。

int *pi=new int;        //pi指向一个动态分配的,未初始化的无名对象
可以是使用直接初始化方式来初始化一个动态分配的对象。
操作说明shared_ptr<T> sp , unique_ptr<T> up空智能指针,可以指向类型为T的对象p将p用作一个条件判断,若p指向一个对象,则为true*p解引用p,获得它指向的对象p->mem等价于(*p).memp.get()返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了swap(p,q) ,p.swap(q)交换p和q中的指针

shared_ptr类

创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。

shared_ptr<string> p1;shared_ptr<list<int>> p2;

shared_ptr独有的操作

操作说明make_shared<T>(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象shared_ptr<T>p(q)p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*p=qp和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放p.unique()若p.use_count()为1,返回true;否则返回falsep.use_count()返回与p共享对象的智能指针数量;可能很慢,主要用于调试

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

shared_ptr<int> p3=make_shared<int>(42);auto p6=make_shared<vector<string>>();

shared_ptr的拷贝和赋值 
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。 
shared_ptr自动销毁所管理的对象 
shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。 
shared_ptr还会自动释放相关联的内存 
return会对shared_ptr指针的引用次数进行递增操作。 
使用了动态生存期的资源的类 
程序使用动态内存出于以下三种原因之一: 
1. 程序不知道自己需要使用多少对象 
2. 程序不知道所需对象的准确类型 
3. 程序需要在多个对象间共享数据

shared_ptr和new结合使用

接受指针参数的智能指针构造函数是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> clone(int p){    return shared_ptr<int>(new int(p)); //正确}

定义和改变shared_ptr的其他方法

操作说明shared_ptr<T> p(q)p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型shared_ptr<T> p(u)p从unique_ptr u那里接管了对象的所有权;将u置为空shared_ptr<T> p(q,d)p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替deleteshared_ptr<T> p(p2,d)p是shared_ptr p2的拷贝,唯一的区别是p将用可调用对象d来代替deletep.reset()p,reset(q)p,reset(q,d)若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则将p置为空。若还传递了参数d,将会调用d而不是delete来释放q

不要混合使用普通指针和智能指针

void process(shared_ptr<int> ptr){}
process的参数是传值方式传递的,因此实参会被拷贝到ptr中。拷贝一个shared_ptr会递增其引用计数,因此,在process运行过程中,引用计数值至少为2.当process结束时,ptr的引用计数会递减,但不会变为0.因此当局部变量ptr被销毁时,ptr指向的内存不会被释放。 
正确方式是传递给它一个shared_ptr:
shared_ptr<int> p(new int(42));     //引用计数为1process(p);                     //
虽然不能传递给process一个内置指针,但可以传递给它一个(临时的)shared_ptr,这个shared_ptr是用一个内置指针显式构造的。但是,这样做很可能会导致错误:
int *x(new int(1024));              //危险:x是一个普通指针,不是一个智能指针process(x);                     //错误process(shared_ptr<int>(x));        //合法的,但内存会被释放int j=*x;                           //未定义的:x是一个空悬指针
不要使用get初始化另一个智能指针或为智能指针赋值 
智能指针类型顶一个了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况儿设计的:我们需要向不能使用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete此指针。
shared_ptr<int> p(new int(42)); //引用计数为1int *q=p.get();             //正确:但使用q时要注意,不要让它管理的指针被释放{//未定义:两个独立的shared_ptr指向相同的内存    shared_ptr<int>(q);}//程序块结束,q被销毁,它指向的内存被释放int foo=*p;                 //未定义:p指向的内存已经被释放了

智能指针和异常

如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。 
智能指针和哑类 
使用我们自己的释放操作 
为了正确使用智能指针,我们必须坚持一些基本规范:

  • 不适用相同的内置指针初始化(或reset)多个智能指针。
  • 不delete get()返回的指针。
  • 不使用get()初始化或reset另一个智能指针。
  • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
  • 如果你用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unique_ptr

一个unique_ptr“拥有”它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。 
与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。尅死shared_ptr,初始化unique_ptr必须采用直接初始化形式:

unique_ptr<double> p1;      //可以指向一个double的unique_ptrunique_ptr<int> p2(new int(42));//p2指向一个值为42的int

由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作 
unique_ptr操作

操作说明unique_ptr<T> u1,unique_ptr<T,D> u2空unique_ptr,可以指向类型为T的对象,u1会使用delete来释放它的指针;u2会使用一个类型为b的可调用对象来释放它的指针unique_ptr<T,D> u(d)空unique_ptr,指向类型为T的对象,用类型为D的对象d代替deleteu=nullptr释放u指向的对象,将u置为空u.release()u放弃对指针的控制权,返回指针,并将u置为空u.reset()释放u指向的对象u.reset(q) ,u.reset(nullptr)如果提供了内置指针q,令u指向这个对象;否则将u置为空
//将所有权从p1转移给p2unique_ptr<string> p2(p1.release());        //release将p1置为空unique_ptr<string> p3(new string(“Trex”));//将所有权从p3转移给p2p2.reset(p3.release());;                    //reset释放了p2原来指向的内存

传递unique_ptr参数和返回unique_ptr 
不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr。 
向unique_ptr传递删除器

weak_ptr

weak_ptr指向由一个shared_ptr管理的对象。将一个weak_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=pp可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象w.reset()将w置为空w.use_count()与w共享对象的shared_ptr的数量w.expired()若w.use_count()为0,返回true,否则返回falsew.lock()如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

动态数组

new和delete运算符一次分配/释放一个对象,但某些应用需要一次为很多对象分配内存的功能。例如,vector和string都是在连续内存中保存它们的元素,因此,当容器需要重新分配内存时,必须一次性为很多元素分配内存。 
为了支持这种需求,C++语言和标准库提供了两种一次分配一个对象数组的方法:C++语言定义了动态数组的new方式;标准库中包含了一个名为allocator的类。

new和数组

int *pia=new int[get_size()];       //pia指向第一个int
分配一个数组会得到一个元素类型的指针 
虽然我们通常称new T[]分配的内存为“动态数组”,但我们用new分配一个数组时,并未得到一个数组类型的对象,而是一个数组元素类型的指针。 
由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end。这些函数使用数组维度来返回指向首元素和尾后元素的指针。出于相同的原因,也不能用范围for语句来处理动态数组中的元素。 
初始化动态分配对象的数组 
可以对数组中的元素进行值初始化,方法是在大笑之后跟一对空括号:
int *pia=new int[10];           //10个未初始化的intint *pia2=new int[10]();            //10个值初始化为0的int
动态分配一个空数组是合法的 
虽然我们不能创建一个大小为0的静态数组对象,但当n等于0时,调用new[n]是合法的:
char arr[0];                //错误char *cp=new char[0];       //正确
释放动态数组 
为了释放动态数组,我们使用一种特殊形式的delete——在指针前加上一个空方括号对:

delete p;               //p必须指向一个动态分配的对象或为空delete [] pa;           //pa必须指向一个动态分配的数组或为空

数组中的元素按逆序被销毁。 
智能指针和动态数组 
为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号:

//up指向一个包含10个未初始化int的数组unique_ptr<int[]> up(new int[10]);up.release();                       //自动用delete[]销毁其指针

指向数组的unique_ptr 
指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符) 
其他unique_ptr操作不便

操作说明unique_ptr<T[]> uu可以指向一个动态分配的数组,数组元素类型为Tunique_ptr<T[]> u(p)u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*u[i]返回u拥有的数组中的位置i处的对象,u必须指向一个数组

allocator类

allocator类

标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。 
类似vector,allocator是一个模板。为了定义一个allocator对象,我们必须指明这个allocator可以分配的对象类型。当一个allocator对象分配内存时,他会根据给定的对象类型来确定恰当的内存大小和对齐位置:

allocator<string> alloc;                //可以分配string的allocator对象auto const p=alloc.allocate(n);     //分配n个未初始化的string

标准库allocator类及其算法

操作说明allocator a定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存a.allocate(n)分配一段原始的、为构造的内存,保存n个类型为T的对象a.deallocate(p,n)释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroya.construct(p,args)p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象a.destroy(p)p为T*类型的指针,此算法对p指向的对象执行西沟函数

allocator分配为构造的内存 
allocator分配的内存是未构造的,我们按需要在此内存中构造对象。

auto q=p;                   //q指向最后构造的元素之后的位置alloc.construct(q++);           //*q为空字符串alloc.construct(q++,10,’c’);    //*q为ccccccccccalloc.construct(q++,”hi”);      //*q位hi!

为了使用allocate返回的内存,我们必须用construct构造对象。使用为构造的内存,其行为是未定义的。 
我们只能对真正构造了的元素进行destroy操作 
拷贝和填充未初始化内存的算法 
它们都定义在头文件memory中

allocator算法

这些函数在给定目的位置创建元素,而不是由系统分配内存给它们。

操作说明uninitialized_copy(b,e,b2)从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的为构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝uninitialized_copy(b,n,b2)从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中uninitialized_fill(b,e,t)在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝uninitialized_fill_n(b,n,t)从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的为构造的原始内存,能够容纳给定数量的对象



转载请注明出处:http://blog.csdn.net/ylbs110/article/details/51049586








0 0
原创粉丝点击