9159金沙游艺场

图片 4
网络七层模型
图片 7
[转]JIRA 7.2.6与Confluence 6.0.3的安装与配置之MS SQL Server版

12-动态内存

这是C++11新特性介绍的第五部分,涉及到智能指针的相关内容(shared_ptr,
unique_ptr,
weak_ptr)。当然笔者以前也写过关于c++智能指针部分的知识总结,这次的话是讲到c++11的特性,再次总结一下。

12.1 智能指针

智能指针行为类似普通指针,但它负责自动释放所知的对象。

#include <memory>

shared_ptr :
允许多个指针指向同一个对象,每个指针都会记录有多少个其他指针指向相同的对象

unique_ptr : 某个对象只允许一个指针指向它

weak_ptr : 弱引用的伴随类,指向shared_ptr所管理的对象。


shared_ptr 和 unique_ptr支持的操作

shared_ptr<T> sp;//空指针,可以指向类型为T的对象

unique_ptr<T> up;

if (p){}//若p指向一个对象,条件为true

*p;//解引用p,获取其指向的对象

p->mem;//等价于(*p).mem

p.get();//返回p中保存的指针,与对象的引用计数无关,不要使用它来初始化一个智能指针或赋值

swap(p,q);//交换p,q中的指针

p.swap(q);


shared_ptr独有的操作

make_shared<T>(args);//返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象

shared_ptr<T>
p(q);//p是q的拷贝;递增q中的引用计数,q中的指针必须可转换为T*

p =
q;//pq保存的指针需可以互相转化,递减p的引用计数,递增q的引用计数;当p的引用计数为0,会将管理的原内存释放

p.use_count();//返回与p共享的对象的智能指针个数。

p.unique();//若p.use_count()返回1,则返回true。


make_shared函数会在动态内存中分配一个对象并初始化。

传递的参数必须符合类型的某个构造函数;内置类型若不提供参数,将进行值初始化。

shared_ptr的拷贝auto p(q);

拷贝操作将会递增引用计数的值:

1,用一个shared_ptr初始化另一个shared_ptr

2,将其作为参数传递给一个函数

3,作为函数的返回值

shared_ptr的赋值和销毁,r = q

1,赋值

2,shared_ptr被销毁

以上操作会递减指针引用计数的值,当计数值为0时释放所管理的对象(shared_ptr的析构函数)

在C++中,我们通过new(在动态内存中为对象分配空间并初始化对象)和delete(销毁该对象,并释放内存)直接分配和释放动态内存

不想看toy code的读者可以直接拉到文章最后看这部分的总结。

12.1.2 直接管理内存

int *pi = new int;//内置类型和组合类型的对象的值未定义,默认初始化

string *ps = new string;

int *pi = new int(1024);//直接初始化

string *ps = new string(10,’9′);

vector<int> *p = new vector<int>{0,1,2,3,4,5,6,7,8,9};

int *pi = new int();//值初始化

string *ps = new string();

初始化器

auto p1= new
auto(obj);//根据obj对象推断类型,并用obj初始化,obj只可以拥有一个

动态分配const对象

const int *p=new const int(1024);//需初始化

const string *p = new const string;

int *p=new int;//分配失败,抛出std::bad_alloc

int *p=new (nothrow) int;//分配失败,返回空指针

delete p;//销毁给定指针指向的对象,释放对应的内存

p=nullptr;//重置指针,避免成为空悬指针

释放非new分配的内存或多次释放行为未定义。

如下代码:

shared_ptr

shared_ptr 基本用法

shared_ptr采用引用计数的方式管理所指向的对象。当有一个新的shared_ptr指向同一个对象时(复制shared_ptr等),引用计数加1。当shared_ptr离开作用域时,引用计数减1。当引用计数为0时,释放所管理的内存。

这样做的好处在于解放了程序员手动释放内存的压力。之前,为了处理程序中的异常情况,往往需要将指针手动封装到类中,通过析构函数来释放动态分配的内存;现在这一过程就可以交给shared_ptr去做了。

一般我们使用make_shared来获得shared_ptr。

cout<<“test shared_ptr base usage:”<<endl;

shared_ptr<string> p1 = make_shared<string>;

if(p1 && p1->empty

*p1 = “hello”;

auto p2 = make_shared<string>;

cout<<*p1<<‘ ‘<<*p2<<endl;

cout<<“test shared_ptr use_count:”<<endl;

cout<<“p1 cnt:”<<p1.use_count()<<“tp2
cnt:”<<p2.use_count()<<endl;

auto p3 = p2;

cout<<“p1 cnt:”<<p1.use_count()<<“tp2
cnt:”<<p2.use_count()<<“tp3
cnt:”<<p3.use_count()<<endl;

p2 = p1;

cout<<“p1 cnt:”<<p1.use_count()<<“tp2
cnt:”<<p2.use_count()<<“tp3
cnt:”<<p3.use_count()<<endl;

12.1.3 shared_ptr和new结合使用

shared_ptr<int> p(new
int(42));//接受指针参数的智能指针的构造函数是explicit的,不可将一个内置指针隐式转换为智能指针,必须进行直接初始化

默认情况下,初始化智能指针的普通指针必须指向动态分配的内存(使用delete释放),但可以提供其他代替delete的操作来将智能指针绑定到指向其他类型的指针上。


shared_ptr<T>
p(q);//p管理内置指针q(new分配)所指的对象,q可转换为T*

shared_ptr<T>p(u);//p从unique_ptr接管对象,u置空

shared_ptr<T>p(q,
d);//p结构内置指针q指向的对象,使用可调用对象d来代替delete

shared_ptr<T>p(p1,d);//p是p1(shared_ptr)的拷贝,但使用d代替delete 

p.reset();

p.reset(q);

p.reset(q,d);

若p是唯一指向其对象的shared_ptr,reset会释放此对象,将p置空;若传递了内置指针q,则令p指向q;若还有参数d,会调用d来释放q;会更新引用计数。

共享对象的智能指针的处理:

if (!p.unique()){

p.reset(new
string(*p));//p不是唯一指向对象的指针,但此时要改变p指向的元素,必须创建一个拷贝

}

//对p的对象进行操作


int *pi = new int;//pi 指向一个未初始化的int

shared_ptr 和 new

shared_ptr可以使用一个new表达式返回的指针进行初始化。

cout<<“test shared_ptr and new:”<<endl;

shared_ptr<int> p4(new int;

//shared_ptr<int> p5 = new int; // wrong, no implicit
constructor

cout<<*p4<<endl;

但是,不能将一个new表达式返回的指针赋值给shared_ptr。

另外,特别需要注意的是,不要混用new和shared_ptr!

void process(shared_ptr<int> ptr)

{

cout<<“in process
use_count:”<<ptr.use_count()<<endl;

}

cout<<“don’t mix shared_ptr and normal pointer:”<<endl;

shared_ptr<int> p5(new int;

process;

int v5 = *p5;

cout<<“v5: “<<v5<<endl;

int *p6 = new int;

process(shared_ptr<int>;

int v6 = *p6;

cout<<“v6: “<<v6<<endl;

上面的程序片段会输出:

in process use_count:2

v5: 1024

in process use_count:1

v6: 0

可以看到,第二次process
p6时,shared_ptr的引用计数为1,当离开process的作用域时,会释放对应的内存,此时p6成为了悬挂指针。

所以,一旦将一个new表达式返回的指针交由shared_ptr管理之后,就不要再通过普通指针访问这块内存!

12.1.4 智能指针和异常

1,不使用相同的内置指针初始化(或reset)多个智能指针

2,不delete get()返回的指针

3,不使用get() 初始化或reset另一个智能指针

4,使用get()返回的指针,当其对应的最后一个智能指针销毁后,get()返回的指针无效了

5,使用智能指针管理不是有new分配的内存,需要有附加的删除器

有些人有这样的疑问,指针一定要new吗?其实指针和new没有什么关系。这里的new在动态内存里为对象分配了内存空间,并返回了一个指向该对象的指针。new是申请堆空间,不是在栈上分配内存,指针只要指向有效的内存空间就可以。比如:

shared_ptr.reset

shared_ptr可以通过reset方法重置指向另一个对象,此时原对象的引用计数减一。

cout<<“test shared_ptr reset:”<<endl;

cout<<“p1 cnt:”<<p1.use_count()<<“tp2
cnt:”<<p2.use_count()<<“tp3
nt:”<<p3.use_count()<<endl;

p1.reset(new string;

cout<<“p1 cnt:”<<p1.use_count()<<“tp2
cnt:”<<p2.use_count()<<“tp3
cnt:”<<p3.use_count()<<endl;

12.1.5 unique_ptr

某个时刻只有一个unique_ptr指向一个对象,unique_ptr被销毁时,对象也被销毁。

定义的同时必须初始化,必须采用直接初始化。不支持拷贝和赋值

unique_ptr<int> p4;

unique_ptr<int> p1(new int(1024));

int *p2=new int(1203);

unique_ptr<int> p3(p2);


unique_ptr特有的操作

unique_ptr<T> u1;//

unique_ptr<T, D> u2;//u2使用类型为D的可调用对象释放指针

unique_ptr<T, D> u(d);//使用类型为D的对象代替delete

unique_ptr<T, D> u(p,
d);//使用普通指针p初始化u,销毁时使用D类型的对象代替delete

u = nullptr;//释放u所指的对象,将u置空

u.release();//u放弃对指针的控制权,返回指针,并将u置空;可用来初始化另一个智能指针或赋值

u.reset();//释放u所指的对象

u.reset(q);//释放u所指的对象,u指向这个内置指针绑定的对象

u.reset(nullptr);//释放u所指的对象,将u置空;

release和reset可以将对象的所有权转移到另一个unique_ptr上。

unique_ptr<string> p(p1.release());//p1置空,p管理p1的对象

unique_ptr<string> p2(new string(“haha”));

p.reset(p2.release());//p释放了原指向的内存,重新指向了p2的内存,p2为空


可以拷贝或赋值一个将要被销毁的unique_ptr,比如从函数返回一个unique_ptr或返回局部unique_ptr的拷贝;

int i;

int *p = &i;

//p可以直接使用了

shared_ptr deleter

可以定制一个deleter函数,用于在shared_ptr释放对象时调用。

void print_at_delete

{

cout<<“deleting…”<<p<<‘t'<<*p<<endl;

delete p;

}

cout<<“test shared_ptr deleter:”<<endl;

int *p7 = new int;

shared_ptr<int> p8(p7, print_at_delete);

p8 = make_shared<int>;

12.1.6 weak_ptr

和某些shared_ptr共享同一个对象,但不会增加shared_ptr的引用计数。weak_ptr不会控制对象的生存期。


weak_ptr<T> w;

weak_ptr<T>
w(sp);//w和sp(一个shared_ptr)指向相同的对象,T必须是可以转换为sp指向的类型。

w = p;//p可以是shared_ptr或weak_ptr,赋值后w,p共享对象

w.reset();//将w置为空

w.use_count();//与w共享对象的shared_ptr数量

w.expired();//若w.use_count()为0,则为true

w.lock();//若w.expired()返回true,则返回空的shared_ptr;否则返回一个w指向对象的shared_ptr


不可用weak_ptr直接返回对象,必须用lock;

auto p = make_shared<int>(100);

weak_ptr<int> wp(p);

if (shared_ptr<int> np = wp.lock()){

//np和p共享同一个对象

}

new直接初始化对象:

unique_ptr

unique_ptr基本用法

unique_ptr对于所指向的对象,正如其名字所示,是独占的。所以,不可以对unique_ptr进行拷贝、赋值等操作,但是可以通过release函数在unique_ptr之间转移控制权。

cout<<“test unique_ptr base usage:”<<endl;

unique_ptr<int> up1(new int;

cout<<“up1: “<<*up1<<endl;

unique_ptr<int> up2(up1.release;

cout<<“up2: “<<*up2<<endl;

//unique_ptr<int> up3; // wrong, unique_ptr can not copy

//up2 = up1; // wrong, unique_ptr can not copy

unique_ptr<int> up4(new int;

up4.reset(up2.release;

cout<<“up4: “<<*up4<<endl;

unique_ptr 作为参数和返回值

上述对于拷贝的限制,有两个特殊情况,即unique_ptr可以作为函数的返回值和参数使用,这时虽然也有隐含的拷贝存在,但是并非不可行的。

unique_ptr<int> clone

{

return unique_ptr<int>(new int;

}

void process_unique_ptr(unique_ptr<int> up)

{

cout<<“process unique ptr: “<<*up<<endl;

}

cout<<“test unique_ptr parameter and return
value:”<<endl;

auto up5 = clone;

cout<<“up5: “<<*up5<<endl;

process_unique_ptr);

//cout<<“up5 after process: “<<*up5<<endl; // would
cause segmentfault

这里的std::move函数,以后再单独具体细说^_^

12.2 动态数组

使用new T[] 和delete[]

T *p = new
T[n];//n必须是整型,不必是常量。n可以为0,返回合法的非空指针。

typedef int  arrInt[100];

int *p = new arrInt;

注意p是元素的指针而不是数组的指针,并且严格说动态数组非数组,只是一段有类型的连续的存。

默认情况下,创建的动态数组执行默认初始化;

int *p1 = new int[10];//值未定义,默认初始化

int *p2 = new int[10]();//值初始化,0

直接初始化

int *p3 = new int[10]{1,2,3,4,5,6,7,8};

delete []
p1;//添加[]和去掉[],需要看p1是单个元素的指针还是动态数组的指针,否则delete操作未定义


可直接使用unique_ptr管理动态数组

unique_ptr<int[]> up(new int[10]);

up.release();//自动调用delete[]销毁

指向数组的unique_ptr不支持成员访问运算符。

unique_ptr<T[]> u;

unique_ptr<T[]>
u(p);//内置指针p指向动态数组,p必须可以转换为类型T*

up[i];//需使用下标来访问


shared_ptr不支持直接管理动态数组,但可以定义自己的删除器,间接管理。

shared_ptr<int> sp(new int[10], [] (int *p){ delete []
p;});

sp.reset();//使用定义时的lambda作为删除器

sp.get();//借用此指针访问动态数组的值

int *pi = new int;//pi指向值为128的对象

string *ps = new string(“christian”);//*ps 指向“christian”的字符串

unique_ptr deleter

unique_ptr同样可以设置deleter,和shared_ptr不同的是,它需要在模板参数中指定deleter的类型。好在我们有decltype这个利器,不然写起来好麻烦。

cout<<“test unique_ptr deleter:”<<endl;

int *p9 = new int;

unique_ptr<int, decltype(print_at_delete) *> up6(p9,
print_at_delete);

unique_ptr<int> up7(new int;

up6.reset(up7.release;

12.2.2 allocator 类

#include<memory>

allocator类将内存分配和对象构造分离出来,类型感知的内存分配方法,分配的内存是原始的,未分配的。


allocator<T> a;//a可以为类型为T的对象分配内存

auto p = a.allocator(n);//分配n个未经构造的内存,保存n个类型为T的对象

a.construct(p,
args);//p是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,args为参数列表

a.destory(p);//p为类型为T*的指针,对p所指对象执行析构函数,必须是构造过得

a.deallocator(p,n);//p是由allocator返回的指针,n是创建时的大小;释放从p开始的n个类型为T的对象,在此之前必须为每个内存调用destory;


拷贝和填充未初始化内存

uninitialized_copy(b,e,b2);//将范围[b,e)的元素拷贝到b2指向的未构造的内存

uninitialized_copy_n(b,n,b2);//

uninitialized_fill(b,e,t);//在[b,e)指向的原始内存开始创建n个对象,对象的值均为t的拷贝

uninitialized_fill_n(b,n,t);

返回指向最后一个构造的元素的下一位置。

new分配const对象必须进行初始化,并且返回的是一个指向const对象的指针:

weak_ptr

weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的对象,但是却不增加对象的引用计数。这样就有可能出现weak_ptr所指向的对象实际上已经被释放了的情况。因此,weak_ptr有一个lock函数,尝试取回一个指向对象的shared_ptr。

cout<<“test weak_ptr basic usage:”<<endl;

auto p10 = make_shared<int>;

weak_ptr<int> wp1;

cout<<“p10 use_count: “<<p10.use_count()<<endl;

//p10.reset(new int; // this will cause wp1.lock() return a false obj

shared_ptr<int> p11 = wp1.lock();

if cout<<“wp1: “<<*p11<<” use count:
“<<p11.use_count()<<endl;

const int *p = new const int;//分配并初始化一个const int;

总结

shared_ptr采用引用计数的方式管理所指向的对象。

shared_ptr可以使用一个new表达式返回的指针进行初始化;但是,不能将一个new表达式返回的指针赋值给shared_ptr。

一旦将一个new表达式返回的指针交由shared_ptr管理之后,就不要再通过普通指针访问这块内存。

shared_ptr可以通过reset方法重置指向另一个对象,此时原对象的引用计数减一。

可以定制一个deleter函数,用于在shared_ptr释放对象时调用。

unique_ptr对于所指向的对象,是独占的。

不可以对unique_ptr进行拷贝、赋值等操作,但是可以通过release函数在unique_ptr之间转移控制权。

unique_ptr可以作为函数的返回值和参数使用。

unique_ptr同样可以设置deleter,需要在模板参数中指定deleter的类型。

weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的对象,但是却不增加对象的引用计数。

weak_ptr有一个lock函数,尝试取回一个指向对象的shared_ptr。

注:另外本人从事在线教育多年,将自己的资料整合建了一个QQ群,对于有兴趣一起交流学习c/c++的初学者可以加群:941636044,里面有大神会给予解答,也会有许多的资源可以供大家学习分享,欢迎大家前来一起学习进步!

当然new申请内存分配的时候也不是都会成功的,一旦一个程序用光了他的所有可用内存(虽然这种情况一般很少发生),new表达式就会失败。这时候会抛出bad_alloc异常。所以我们需要通过delete来释放占用的内存。在这里注意delete并不是要删除指针,而是释放指针所指的内存。

int i;

int *pi = &i;

string str = “dwarves”;

double *pd = new double;

delete str; // 错误:str不是一个指针

delete pi; // 错误:pi指向一个局部变量

delete pd; // 正确

使用new和delete来管理动态内存常出的一些错误:

1.忘记delete,即导致了“内存泄漏”,

2.野指针。在对象已经被释放掉之后,(这里注意,此时的指针成为了悬垂指针,即指向曾经存在的对象,但该对象已经不再存在。结果未定义,而且难以检测。)这时候我们再次使用,会产生使用非法内存的指针。

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图