内存
栈内存 V.S. 堆内存
– 栈内存的特点:更好的局部性,对象自动销毁
– 堆内存的特点:运行期动态扩展,需要显式释放
- 内存的使用 ```cpp int fun() { //正确使用 int res = new int[2]; return res; //错误使用:这里返回了临时对象,x在栈内存中,随着栈帧的自动销毁而销毁 int x = 2; return &x; }
int main() { int y = fun(); std::cout << y <<std::endl; //…. delete y; }
- 在 C++ 中通常使用 new 与 delete 来构造、销毁对象
- 对象的构造分成两步:分配内存与在所分配的内存上构造对象;对象的销毁与之类似
- 构造:
- new 分配内存
- 赋值2即构造对象
- 销毁:
- 销毁对象->类的析构函数
- 归还内存给系统
<a name="wsR8I"></a>
#### new 的几种常见形式
- – 构造单一对象 / 对象数组
- int *y = new int[2]; //1.分配内存,2个int(连续的2*4个字节) 2.缺省初始化
- int *y = new int; //1.分配内存,2.缺省初始化
- int *y = new int[5]{1,2,3,4,5};// 列表初始化
- int *y = new int[5]{}; //列表初始化为0;
- – nothrow new
- int *y = new(std::nothrow) int[5]{};
- 分配不成功的时候y指向nullptr;
- 不加nothrow,分配不成功的时候跳转到异常抛出程序
- – placement new
- 使用堆上现有的内存
```cpp
char ch[sizeof(int)];
int* y = new(ch) int(4); //把ch地址赋值给y,注意ch内存大小要大于y的内存
- – new auto
- int *y = new auto(3); //正确,根据3推导出int类型
- int *y = new auto; //错误,auto无法适应等号前面的int
new 与对象对齐
- 可以确保对齐信息 ```cpp struct alignas(256) Str{}; //alignas:对齐,Str的地址为256的倍数,即最后两位为00 int main() { Str* ptr = new Str();
}
<a name="MUkSp"></a>
#### delete 的常见用法
– 销毁单一对象 / 对象数组
- delete ptr; //销毁单一对象
- delete []ptr; //销毁数组
– placement delete
- 只销毁对象,不归还内存:类调用析构函数,内建数据类型无操作
<a name="oZhrM"></a>
#### 使用 new 与 delete 的注意事项
– 根据分配的是单一对象还是数组,采用相应的方式销毁<br />– delete nullptr //delete什么都不做
```cpp
int *x = nullptr;
if(...)
{
x = new int[3];
}
delete x;
//如果进入if则走正常delete流程
//如果不进入if,x为空指针,delete nullptr实际上什么都没做
– 不能 delete 一个非 new 返回的内存
– 同一块内存不能 delete 多次
调整系统自身的 new / delete 行为
- 不要轻易使用
- 不建议替换全局的new
- 类中可以使用operator new,详见cppreference
智能指针
使用 new 与 delete 的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况
- new和delete不在同一块语句中 ```cpp //所有权不清晰的情况 int fun() { int res = new int[2]; return res; }
int main() { int y = fun(); std::cout << y <<std::endl; //…. delete y; }
C++ 的解决方案:智能指针<br />– auto_ptr ( C++17 删除)<br />– shared_ptr / uniuqe_ptr / weak_ptr
<a name="FhZMe"></a>
## shared_ptr
基于引用计数的共享内存解决方案<br />– 基本用法
```cpp
share_ptr<int> x(new int(3)); //引用计数,自动销毁
share_ptr<int> y = x; //计数加1
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(3));
return res;
}
int main()
{
std::shared_ptr<int> x = fun();
}
//安全引用,不用担心少or多销毁的情况,计数为0的时候自动销毁
– reset / get 方法
- x.get(); //返回int 类型,而非share_ptr
,用于兼容int 的使用情况 - x.reset(new int(4)); //删除原始内存指向新的地址
- x.reset() //删除原始内存,不指向新的地址
– 指定内存回收逻辑
void fun(int *ptr)
{
delete ptr;
}
int main()
{
std::shared_ptr<int> x(new int(3),fun);
}
//自定义回收逻辑
//用法2:自定义回收逻辑,可以避免res静态内存被错误回收
void dummy(int*) {}
share_ptr<int> fun(){
static int res = 3;
return std::shared_ptr<int>(&res,dummy);
}
int main()
{
std::shared_ptr<int> x = fun();
}
– std::make_shared
- std::shared_ptr
x(new int(3),fun); - shared_ptr
x = make_shared (3); //建议使用make_shared,构造传参的方式,可以把计数和内存放在尽量近的位置
– 支持数组( C++17 支持 shared_ptr
– 注意: shared_ptr 管理的对象不要调用 delete 销毁
int *ptr = new int(3);
std::shared_ptr<int> x(ptr);
std::shared_ptr<int> y(ptr); //错误,会导致重复删除
std::shared_ptr<int> y(x.get()); //错误,会导致重复删除
//直接传入内存地址,不会使计数+1,删除的时候容易导致重复删除
unique_ptr
—— 独占内存的解决方案
– 基本用法
- unique_ptr
y = x; //错误用法 - unique_ptr
y = std::move(x); //正确用法
– unique_ptr 不支持复制,但可以移动
– 为 unique_ptr 指定内存回收逻辑
weak_ptr
—— 防止循环引用而引入的智能指针
– 基于 shared_ptr 构造
– lock 方法