动态内存与智能指针

1.动态内存与智能指针

静态内存:存储局部static对象,类static对象,以及定义在任何函数之外的对象,使用时创建,程序结束时销毁。

栈内存:保存在函数内的非static对象,包括函数形参,函数内局部变量。

堆内存:动态分配的内存,在程序运行时分配的对象,动态对象的生存期由程序来控制。

2.智能指针

  • 常规指针:new 和 delete 需要显式调用,因此如果调用不及时容易出现内存泄漏(等以后出一篇容易造成内存泄露的举例及解决)。
  • 智能指针:shared_ptr:允许多个指针指向同一个对象。 unique_ptr:独占指向的对象。weak_ptr:弱引用

2.1 shared_ptr类

智能指针使用方式与普通指针一样。

	shared_ptr<string> p1;
	if (p1 && p1->empty())
	{
        //如果p1不为空,是一个空string,则赋予其一个新值
		*p1 = "hi";
	}

make_shared函数

在动态内存中分配一个对象并且初始化它,返回指向此对象的shared_ptr

在make_shared后面加上一个<>给出指定类型:类似于emplace成员,在make_shared需要用参数来构造指定的对象,否则进行值初始化。

	//指向一个值为42的int的shared_ptr
	shared_ptr<int> p1 = make_shared<int>(42);
	shared_ptr<string> p2 = make_shared<string>(10, '*');//10个*
	shared_ptr<int> p3 = make_shared<int>();	//值初始化,0
	//使用auto
	auto p4 = make_shared<int>(5);	//5

shared_ptr的拷贝与赋值

每个shared_ptr都会记录有多少个相同的对象。

p和q只想相同的对象,此对象有两个引用者

	shared_ptr<int> p = make_shared<int>(10);
	auto q(p);

引用计数:每个shared_ptr都有一个关联的计数器。

拷贝,初始化,作为函数参数,函数返回,计数器都会递增

赋予新值,被销毁,局部shared_ptr离开作用域,计数器就会递减

当计数器的计数为0时,就会自动释放他所管理的内存空间。

	shared_ptr<int> q = make_shared<int>(5);
	shared_ptr<int> p = make_shared<int>(10);
	auto q=p;	//q赋值为p,p的计数器递增,q的计数器递减;q计数器为0,即5被释放。

shared_ptr的析构函数用来管理它所指向对象的引用,并且管理计数器,计数器为0,则析构函数会自动销毁对象,释放内存。


作为函数返回

返回一个shared_ptr对象:

shared_ptr<int> factory(int arg)
{
	//.......
	return make_shared<int>(arg);
}

p在局部变量中创建,并没有返回,离开函数作用域时,p销毁,自动释放内存:

void factory(int arg)
{
	//.......
	shared_ptr<int> p=make_shared<int>(arg);
}

下面返回了一个p的拷贝,拷贝会使得计数器递增,p被销毁,但是并没有释放内存:

shared_ptr<int> factory(int arg)
{
	//.......
	shared_ptr<int> p=make_shared<int>(arg);
    
    return p;
}

确保在无用之处及时释放shared_ptr对象,例如在容器中,unqie重排元素,之后的元素便不需要,则需要使用erase删除无用的元素

2.2 直接管理内存

new分配与初始化对象

采用直接初始化或者默认初始化,也可以使用列表初始化。

int* p=new int
int* p=new int(10);
string* s=new string("woaini");
vector<string>* ps=new vector<string>{"abc","dbg","dad","polkd"};

使用auto推断想要分配的对象类型:只能有单一的初始化器

	auto x = new auto("abc");		//const char* 类型

动态分配const对象:

	const int* a = new const int(10);
	const string* b = new const string("abc");
	auto c = new const string("5");

分配失败的情况:

	int* p1 = new int;				//分配失败,new抛出bad_alloc
	int* p2 = new (nothrow) int;	//分配失败,new返回一个空指针

delete释放

delete通常不会分辨指向的是一个静态还是动态分配的对象,也不会指出是否释放成功。

delete可以释放const对象:

	const int* p = new const int(10);
	delete p;

由内置指针管理的动态内存在被显式释放前一直都会存在。

void factory(int arg)
{
	//.......
	shared_ptr<int> p=make_shared<int>(arg);
    
   	delete p;		//显式释放
}
shared_ptr<int> factory(int arg)
{
	//.......
	shared_ptr<int> p=make_shared<int>(arg);
    
    return p;		
}

delete p;	//记得在后面释放内存

ps:1. 忘记delete内存

  2. 使用已经释放掉的对象
  	3. 同一块内存释放两次

delete之后重置指针值

	const int* p = new const int(10);
	delete p;
	p = nullptr;		//重置指针为空

delete的局限与bug

	const int* p = new const int(10);
	auto q = p;				//q和p被绑定到同一个对象
	delete p;				//释放p,q也被释放
	p = nullptr;			//只把p设置为空指针,但是q仍然保存着已经释放了的动态内存地址

智能指针与普通指针的不同:

	int* q = new int(42), * p = new int(100);
	p = q;		//普通指针,进行赋值后,指向新的内存空间,但是原来的内存会一直保留,造成内存泄露
	auto q2 = make_shared<int>(42), p2 = make_shared<int>(100);
	p2 = q2;	//智能指针,进行赋值后,会自动递减计数器,释放原有内存

2.3 shared_ptr和new综合使用

使用普通指针初始化智能指针: 智能指针的构造函数时explicit的,不支持隐私,所以不能使用拷贝初始化的形式,只能使用直接初始化的形式。

shared_ptr<int> p1=new int(10);		//错误
shared_ptr<int> p1{new int(10)};	//正确,只能使用直接初始化的形式

包括在函数的返回值中:必须显式的创建shared_ptr< int >,来转换为智能指针。

构建智能指针shared_ptr时,要使用make_shared来创建,因此分配的对象就能直接与shared_ptr绑定,避免了无意间将一块内存绑定到多个独立创建的shared_ptr之上。

普通指针与智能指针传递给函数的注意事项

	void process(shared_ptr<int> ptr)
	{	//这是一个函数体
		//.... 
	}
	//正确方式:
	shared_ptr<int> p(new int(400));
	process(p);
	int i = *p;
	//错误方式:
	int* x(new int(42));			//危险:x不是智能指针
	process(x);						//错误,不能将int* 转换为shared_ptr
	process(shared_ptr<int>(x));	//合法,但是内存会被释放
	int j = *x;						//未定义,x是一个空悬指针

临时的shared_ptr传递给函数的情况: 当这个临时的shared_ptr在函数内执行完毕后,被销毁,计数器递减,为0,会释放其所指向的内存空间;此时再让一个j去访问一个已经释放了的空间是不合法的。

get函数

返回一个内置指针,指向智能指针所管理的对象。

作用:我们要向不能使用智能指针的代码传递一个内置指针

使用get返回的指针不能delete释放:会使得原智能指针也被delete一次,智能指针成为空悬指针,然后其销毁时,会再次释放一次,造成delete了两次!!!

只有确保代码不会被delete时,才能使用get。 并且永远不要用get初始化另一个智能指针或者为智能指针赋值

	shared_ptr<int> p(new int(100));
	int* q = p.get();		// p 和 q共享一个内存的使用权
	//shared_ptr<int> s = p.get();	//错误

	delete q;				//释放q,导致p也被释放
	int foo = *p;

	return 0;				//在最后p释放时,已经指向了无效的区域,发生崩溃

reset操作

使用reset来将一个新的指针赋予一个shared_ptr。

	shared_ptr<int> p(new int(100));
	p.reset(new int(50));		//指向新的内存空间

reset会更新引用计数,会释放原来的内存空间。

与unique配合使用,来控制多个shared_ptr共享的对象。

在改变底层对象之前,先检查是否有当前对象的唯一控制权,如果不是,则创建一份新的拷贝,让其单独到一个新的内存空间里去:

	shared_ptr<int> p(new int(100));
	auto q(p);		//p  q 共享控制权
	if (!p.unique())	//不是唯一控制的
	{
		p.reset(new int(*p));	//更新内存空间位置
	}
	*p += 10;		//在改变底层对象

2.4 智能指针和异常

智能指针在程序结束或者是异常终止,都能将其释放。但是内置指针如果在delete之前被终止,则内存空间不会被释放。

使用自己的释放操作:制作删除器:完成对shared_ptr中保存的指针进行释放的操作(相当于析构函数的操作)。

ps:

  1. 不适用相同的内置指针值初始化多个智能指针
  2. 不delete 或者 get返回的指针
  3. 不适用get初始化或reset另一个智能指针
  4. 使用get返回的指针,在对应的最后一个智能指针销毁后,你的内置指针就无效了
  5. 使用智能指针管理的资源,不是new分配的内存,要传递给他一个删除器

2.5 unique_ptr

unique_ptr:拥有它所指向的对象,某个时刻,只能有一个unique_ptr指向一个给定的对象

初始化必须采用直接初始化的方式。

unique_ptr<int> p(new int(100));

unique_ptr拥有其对象, 因此不能普通共享

	unique_ptr<int> p{ new int(100) };
	unique_ptr<int> q{ p };	//不支持拷贝
	unique_ptr<int> c;
	c = p;					//不支持赋值

可以借助reset和release完成共享:

  • release:释放其指向的对象,将本身置为空,返回其原来所指的对象
  • reset(q):另其指向q对象
	unique_ptr<int> p{ new int(100) };
	unique_ptr<int> q(p.release());		//转移给q
	unique_ptr<int> c;
	c.reset(q.release());

可以让一个指针指向release的返回值

auto p=q.release()

从函数返回unique_ptr:

unique_ptr<int> Clone1(int p)	//返回临时对象
{
	return unique_ptr<int>(new int(p));		//从函数返回unique_ptr指针
}
unique_ptr<int> Clone2(int p)	//返回局部对象的拷贝
{
	unique_ptr<int> ret(new int(p));
	return ret;
}

传递删除器:

unique_ptr<objT,delT> p(new objT,func);

2.6 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);

弱共享,创建wp不会改变p的引用计数,wp的对象随时都有可能被shared_ptr释放。

shared_ptr使用reset置为空,释放内存空间,weak_ptr与shared_ptr绑定,也跟着一起释放。

  • lock函数:weak_ptr的函数,如果expired为空,返回空weak_ptr;否则返回指向其对象的shared_ptr
  • expired函数:若use_count为空,返回true;否则返回false;
  • use_count函数:返回与shared_ptr共享的数量。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
p.reset();					
// wp.use_count()==1 -> wp.expired()==false  -> wp.lock() 返回其指针对象,解引用得到值
cout << *(wp.lock()) << endl;	//出错,wp为空悬指针

3.动态数组

ps:使用标准库容器而不是动态分配的数组,使用容器更简单,更不容易出现内存管理错误并且可以有更好的性能。

动态数组不是数组类型!!!

new和数组

new分配多个内存空间

int* p = new int[50];	//分配50个int的空间,不必是常量

简便的起别名方式

typedef int arrT[50];	//起别名,arrT表示一个50个int的数组类型
int* p = new arrT;		//分配50个元素的数组

ps:动态数组无法使用begin()和end(),因此也无法使用基于范围for语句,因为分配的内存并不是数组类型

初始化

采用值初始化,()为空则采用默认初始化,可以使用列表初始化,如果给出的元素个数少于分配的数量,则剩下的采用默认值初始化的方式。

int* p = new int[10] {1, 2, 3, 4, 5};

释放动态数组

采用[]的形式,必须要对应[],指明了这是一个数组的释放。

int* p=new int[10];
delete p;

ps:数组中的元素按逆序销毁,即从后往前销毁。

智能指针和动态数组

  • 使用unique_ptr的动态数组

    指向一个包含10个int的数组:

unique_ptr<int[]> p(new int[10]);
p.release();		//调用释放其内存,相当于delete[] p

unique_ptr指向一个数组,就不能使用->和 . 运算符(成员访问运算符),但是可以使用[]下标运算符访问元素:

for (size_t i=0;i!=10;++i)
{
	cout<<p[i]<<" ";
}
  • 使用shared_ptr的动态数组

    必须提供自己定义的删除器

shared_ptr<int[]> q(new int[20], [](int* p) {delete[] p; });
q.reset();	//使用lamdba释放数组,使用delete[]

shared_ptr的动态数组不支持下标,也不支持指针的算术运算

for (size_t i=0;i!=10;++i)
{
	*(q.get()+i) = i;	//转换为内置指针,进行内置指针的解引用与递增
}

allocator类

引入:

new的局限性:将内存分配对象构造组合在一起。

delete的局限性:将对象析构内存释放组合在一起。

  • 我们希望将内存分配和构造对象分离,只有在真正需要时才会执行对象的创建操作。

allocator类

提供了将内存分配和对象构造分离开的方法。

它分配的内存是原始的,未构造的。

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

construct成员函数

接受一个指针和零个或多个额外参数,在给定位置构造一个元素,

allocator<string> alloc;	//可以分配string的allocator对象
auto p = alloc.allocate(10);//可以分配n个未初始化的string
auto q = p;
alloc.construct(q++, 10, 'c');
alloc.construct(q++);
alloc.construct(q++, "hi");
cout << *p << endl;

destroy函数

对每个构造的元素调用destroy来销毁他们,接受一个指针,对指向的对象执行析构函数。

while (q != p)
{
	alloc.destroy(--q);	//释放我们真正构造的string对象
}

deallocate函数

大下参数必须与分配时提供的大小参数一样。

alloc.deallocate(p,10);	//释放内存

拷贝和填充未初始化内存的算法

  • uninitialized_copy(b,e,b2);
  • uninitialized_copy_n(b,n,b2);
  • uninitialized_fill(b,e,t);
  • uninitialized_fill_n(b,n,t);
vector<int> a{ 1,2,3,4,5,6,7,8,9 };
allocator<int> alloc;
//创建一个比容器大两倍的初始内存
auto p = alloc.allocate(a.size() * 2);
//返回得到的q指向最后一个构造元素之后的位置
auto q = uninitialized_copy(a.cbegin(), a.cend(), p);
//填充42
uninitialized_fill_n(q, a.size(), 42);

完整操作:

allocator<string> alloc;
string s;
auto str = alloc.allocate(10);
auto p = str;
while (cin>>s && p != str + 10)
{
	alloc.construct(p++, s);
}
while (p != str)
{
	alloc.destroy(--p);
}
alloc.deallocate(str, 10);

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuleo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值