动态内存与智能指针
文章目录
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:
- 不适用相同的内置指针值初始化多个智能指针
- 不delete 或者 get返回的指针
- 不适用get初始化或reset另一个智能指针
- 使用get返回的指针,在对应的最后一个智能指针销毁后,你的内置指针就无效了
- 使用智能指针管理的资源,不是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);
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209734.html