C++内存管理

动态内存

动态内存与智能指针

在这里插入图片描述

三种不知道情况 动态分配
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();
newallocator
分配+构造+返回
花废了更多构造的时间
没有构造函数的类不能动态分配数组
类型感知内存分配
分配时根据对象确定大小和对齐位置
不构造
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函数,编译器不会异议,但 程序员 就应当承担起相应的责任!!

重载后的查找顺序:操作的类类型成员函数 ⇒ 全局作用域 ⇒ 标准库版本

  • 未完待续
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值