动态内存
动态内存与智能指针
三种不知道情况 动态分配 |
---|
1.不知道 使用多少对象 |
2.不知道 对象准确类型 |
3.多个对象间 共享数据 |
直接管理
new
new三步走:
1.调用 new 的标准库函数分配内存空间 (其中的数据将被保护)
2.构造函数构造,传入初始值
3.返回指向该对象的指针
默认初始化:内置类型、组合类型未定义;类类型使用默认构造函数
int *p1=new int;
直接初始化:
构造方式 或 (新标准的)列表初始化
int *p2=new int(1024);
string *p3=new string(10,'9');
vector<int> *p4=new vector<int>{0,1,2,3,4,5,6,7,8,9};
值初始化:类类型都会使用默认构造函数
string *p4=new string();
const int *p5=new const int();
内存耗尽时返回 bad_alloc异常,使用了nothrow参数后new返回NULL而不是异常
int *p=new(nothrow) int; //称为 定位new(placement new),
delete
delete表达式(expression) 将 动态内存归还系统
重复delete 或 delete 非动态内存 会产生未定义的潜在错误
delete p5; //const 变量不能改变,但可以销毁
内置指针管理的内存,除非显示释放,否则一直存在
int *fun(int i){
return new int(i); //容易被忘记释放的 内置指针
} //需要函数的使用者记得释放内存
//也是为什么推荐 shared_ptr的原因
void fun(double d){
double *dd = new double(d);
return; //指针变量 dd 被销毁,但内存仍然被占用
//应当 delete dd;
}
delete两步走:
1.执行析构函数,销毁对象
2.调用operator delete 标准库函数释放内存空间
空悬指针
(dangling pointer) 保存着已被释放的内存空间的地址
delete后需要保留的话,赋值nullptr
int *p1(new int(42));
auto p2(p1);
delete p1;
p1 = nullptr; //此时p2空悬,因此推荐shared_ptr
忘记释放 ⇒ 内存泄漏,尤其注意多态情境下的虚析构函数
错误释放 ⇒ 非法引用的指针 第二次释放正在被使用的空间
两种智能指针(smart pointer) 自动释放指向的对象
shared_ptr
#include<memory>
shared_ptr; // 允许多指针“共享”对象
unique_ptr; // “独占”对象
weak_ptr; // 弱引用 一种伴随类
shared_ptr<int> p1;//模板类,需要指定类型
shared_ptr<list<int>> p2; //嵌套
//-----公共函数------
p.get(); //返回p所保存的指针
swap(p1,p2);
p1.swap(p2); //交换指针
//-----shared独享------
auto p = make_shared<T>(arg);//arg初始化,并返回shared_ptr对象
//arg必须与T的某个 构造函数 匹配 不传参则值初始化
shared_ptr<T>p1(p2); //p2.use_count()++;
shared_ptr<T>p1(q); //explicit要求传入内置指针显示构造而不能隐式转换p1=p2;
p1=p2; //p1.use_count()--;p2.use_count()++;
//if(p1.use_count()==0)自动释放内存
p.unique(); //if(p1.use_count()==1)return true;else return false;
p.use_count(); //共享一个对象的指针数量,用于调试
shared_ptr<T>P1(uniq); //接管unique指针的所有权,u置空
shared_ptr<T>p1(q,d); //泛型,传入d自定义delete销毁q
引用计数(reference count)的数据结构的实现与STL库相关
shared_ptr的销毁
1.调用shared_ptr析构函数{p.use_count()–;}
2. if(p.use_count()==0)
调用 对象类的析构函数(destructor) 完成销毁工作,释放资源
优点:count()==0,离开局部作用域 时自动销毁
//共享数据
//只要还有shared_ptr引用,对象就不会被销毁!!!应该尽量erase()删除不用的元素,释放内存
//使用场景
class B{
public:
B();
B(initializer_list<string> l);
private:
shared_ptr<vector<string>> data;
void check(size_type i) const; //越界检查
}
B::B():data(make_shared<vector<string>>){}
B::B(initializer_list<string> l):data(make_shared<vector<string>>(l)){}
void B::check(size_type i) const{
if(i>=data->size()||i<0)
throw out_of_range("Out of Range!"); //抛越界异常
}
B b1;
void fun(string s1,string s2){
shared_ptr<B> b2({s1,s2});
b1=b2; //b1.data.use_count=2;
return; //b2被销毁,但是由于智能指针b1的存在,{s1,s2}对象并没有被释放,实现同步
}
智能指针不要和内置指针混用!!!
int *q=new int(0);
void process(shared_ptr<int> p){return;}
process(shared_ptr<int>(q));
//临时变量在当前行的表达式process(shared_ptr<int>(q))结束时也被销毁,use_count=0,释放内存
//出大问题!!!q悬空了!!!
shared_ptr<int> p3=new shared_ptr<int>(q);
shared_ptr<int> p4=new shared_ptr<int>(q);
//p3和p4并没有共享use_count,可能单方面释放内存,出大问题!!
auto p4(q); //拷贝构造use_count++;
除非万不得已,不要使用p.get()初始化其他智能指针!!!
shared_ptr<int> p(new int(0));
int q*=p.get();
{shared_ptr<int>(q);} //程序块结束,临时变量被销毁,空间释放,q悬空!!!
如果程序块内发生异常,可能在释放内存之前就已中断执行,此时使用析构函数能够保证即使在抛出异常的情况下,被占用的资源也能被释放,实现这个功能的函数称为deleter(删除器)
void end_connection(connection *p){disconnection(*p);}
try{
shared_ptr<connection> p(new connection(d),end_connection);
} //离开作用域时,shared_ptr调用end_connection析构释放内存
//即便异常,也会执行析构函数(包括有deleter)
catch(Error){
dosomething();
}
unique_ptr
unique_ptr 销毁时,其对象也被销毁
u.release(); //return ptr,u=NULL,注意release没有释放对象内存
u.reset(); // 释放u指向对象的内存
unique_ptr<T> p1(new T(x)); //需要绑定,直接初始化
//unique_ptr<T> p2(P1); 错位,不支持拷贝
//unique_ptr<T> p2=p1; 错误,不支持赋值
u.reset(p1.release()); //指向内置指针
unique_ptr<T> p3(u.release());
//注意release()并没有释放内存,但转让对象的所有权
例外
unique_ptr不支持拷贝,但可以从函数中返回一个unique_ptr
unique_ptr不支持赋值,但可以从函数中收回动态资源的所有权
unique_ptr<T> fun(){
unique_ptr<T> p=new T();
return p;
}
unique_ptr<T> p=fun();
cut<<*p<<endl;
这是一种特殊的“拷贝”对象移动
删除器重载和shared_ptr不一样
unique_ptr管理删除器的方式不同-----------------运行时绑定
unique_ptr<T,D> p(new T(),d);
//<指针类型,删除器类型> D类对象d
void f(destination &p){
connection c=connect(&d);
unique_ptr<connection,decltype(end_connection)*> p(&c,end_connection);
//泛型, 此时即便异常推出也能调用end_connnection
}
weak_ptr
弱共享,不控制对象的释放
必须绑定到shared_ptr 或 NULL 指针上
weak_ptr<T> w(shared_p);
w=p; // 必 须 使 用 shared_ptr 进 行 初 始 化 !!!!
w.use_count(); //shared_p的数量
w.expired(); //“过期?” shared_p.use_count()==0:true;
w.lock(); //w.expired()==false:return shared_ptr<T> p(w);
// true :return shared_ptr<T> p(NULL);
if(shared_ptr<T>p = w.lock()){} //保证shared是有东西的
作为一种伴随类帮助对 shared_ptr 进行控制,但注意作为伴随类可能需要对象成员的访问权限:友元声明 friend
- 未完待续
动态数组
一次分配一个对象数组
两种方法 都 没有 STL的 vector容器 好。。。还要自己定义拷贝,赋值,构造。。。
new[]
语法层面
任何时候 new 返回 第一个元素的指针!!!
因此不能使用begin()和end()等函数,函数本身依靠数组的维度,范围for()也无法使用
int *p=new int[size()]; //默认初始化
int *p=new int[size()](); //值初始化
int *p=new int[size()]{}; //列表初始化 元素数量>size() 返回<new>::bad_array_new_length
int *p=new string[10]{"a","s"}; //错误,auto不能使用初始化器initializer_list
int *p=new int[0]; //new能工作,但返回一个"尾后指针",合法,非空,就是不能解 引用!!!
delete[]
忘了[]行为未定义的异常
逆序销毁!!
T *p=new T[size()];
T *q=new T(); //内置指针
unique_ptr<T[]> u(q); //数组delete 或者 元素delete[] 都是未定义的结果
u[i]; // 支持下标 !!!!
q.release(); //unique 自动调用 delete [] p;
shared_ptr 不支持动态管理数组
1.无法使用下表“[]”运算符
2.没有动态数组的deletor
3.不支持指针的算数运算
shared_ptr<T> p(new T(),[](int *p){delete []p;}); //参见lambda表达式
//必须提供lamnda表达式删除器
for(size_t i=0;i!=10;++i)
*(p.get()+i)=i; // 通过get()返回内置指针实现迭代器的功能
allocator
< memory > 类
更好的性能和更灵活的管理能力,通常用来实现STL容器,这位大神讲的非常清楚!
相较malloc(),多一步异常检查
if(!p=malloc())
throw bad_malloc();
new | allocator |
---|---|
分配+构造+返回 花废了更多构造的时间 没有构造函数的类不能动态分配数组 | 类型感知内存分配 分配时根据对象确定大小和对齐位置 不构造 |
allocator<T> a; //T类型分配器
T *p = a.allocate(n); //分配原始的未构造的内存,返回第一个元素的指针
//几个函数配合使用
a.construct(p,argument); //arg传给p处构造对象 argument实参 parameter形参
a.destroy(p); //对p析构
a.deallocate(p,n); //n必须和allocate(n)一致,释放内存
//几种 对象 拷贝函数
uinitialized_copy(b,e,b2); //把 b --> e 中的元素从b2开始顺序拷贝
uinitialized_copy_n(b,n,b2); //把 b 开始n个的元素从b2开始顺序拷贝
uinitialized_fill(b,e,t); //向 b --> e 中拷贝t
uinitialized_fill_n(b,n,t); //从 b 开始拷贝n*t
特殊工具
控制内存分配
区别于标准内存管理机制,自定义内存分配细节
核心思想:程序想控制内存分配释放,就重载 operator new 和 operator delete函数,编译器不会异议,但 程序员 就应当承担起相应的责任!!
重载后的查找顺序:操作的类类型成员函数 ⇒ 全局作用域 ⇒ 标准库版本
- 未完待续