版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79266440
C++ 学习笔记(12)动态内存、智能指针、new和delete、动态数组、allocator
参考书籍:《C++ Primer 5th》
API:动态内存管理
- 静态内存:
- 保存局部static对象、类static数据成员、定义在函数外的变量。使用前分配,程序结束时销毁。
- 栈内存:
- 保持函数内的非static对象。存在于程序块中。
- 堆内存(动态分配):
- 程序控制动态对象。
12.1 动态内存与智能指针
- 智能指针(smart pointer):负责自动释放所指向的对象。
- shared_ptr:允许多个指针指向同一个对象。
- unique_ptr:独占一个对象。
- weak_ptr:弱引用,指向shared_ptr所管理的对象(不对shared_ptr产生影响)。
- auto_ptr(C++17后抛弃):具有unique_ptr部分特性,不能在容器中保存,也不能作为函数返回值。
12.1.1 shared_ptr
- 使用原因:
- 常用于允许多个对象共享相同的状态。
- 不知道需要多少对象。
- 不知道所需对象准确类型(继承等)。
shared_ptr<string> p;
if(p && p->emty()) // 检查p是否为空,以及指向的字符串是否为空
*p = "hi"; // 解引用p,赋予新值
make_shared:动态分配一个类型,参数就该类构造函数的参数列表。
引用计数(reference count):shared_ptr的一个关联的计数器。
- 拷贝shared_ptr时,计数器递增:
- 一个shared_ptr初始化另一个shared_ptr。
- 作为参数传递。
- 作为函数返回值。
- shared_ptr赋予新值或者被销毁时,计数器递减:
- 局部shared_ptr离开作用域时。
- 拷贝shared_ptr时,计数器递增:
auto p = make_shared<int>(12); // p 指向一个int,计时器为1。
auto q(p); // q 和 p 指向同一个对象,计数器为2。
auto r = make_shared<int>(12); // r 指向一个int,计数器为1。
r = q; // r被赋值,指向 q。
// q 的计数器递增(为3)。
// r 计数器递减(为0), r 原来指向对象没有引用者,被自动释放。
- shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,其析构函数就会销毁对象,释放内存。
void function()
{
shared_ptr<int> p =make_shared<int>(666); // 计数器为 1。
return p; // 返回 p 时,实际上是返回p的拷贝值,计数器递增为 2。
} // p 离开作用域,计数器递减,变回 1。
- 如果将shared_ptr存放于容器中,对于不需要用的元素,记得要使用erase删除,这样才能释放内存。
12.1.2 直接管理内存
- new:返回指向该对象的指针。
int *p1 = new int; // 默认初始化,*p1的值未定义
int *p2 = new int(); // 值初始化为0,*p2为0
auto p3 = new auto(1); // 正确,推出p1为int *类型
auto p4 = new int{ 1 }; // 正确
auto p5 = new auto{ 1 }; // 错误,不能使用new auto加上花括号
- delete:释放动态分配的内存,也可以是空指针。释放非new分配的内存或者同一指针释放多次的行为是未定义的。
- 在指针被delete之后,会变成空悬指针(dangling pointer),即指向一块已经失效的内存的指针。可以将指针赋予nullptr。
12.1.3 shared_ptr 和 new 结合使用
- 接收指针参数的智能指针构造函数是explicit的,即不能隐式将内置指针转换成智能指针,必须使用直接初始化形式初始化一个智能指针。
shared_ptr p1 = new int(123); // 错误,因为new产生的是内置指针,且智能指针无法隐式转换。
shared_ptr p2(new int(123)); // 正确,使用直接初始化。
if (!p2.unique()) // 如果p2不是唯一用户(计数器为1),重新分配。
p2.reset(new int(123)); // 正确,指向新对象。
- 不要混合使用普通指针和智能指针。
- 不要用get初始化另一个智能指针或赋值。
- get返回普通指针,指向智能指针管理的对象。不能delete这个指针。
void process(shared_ptr<int> p) {} // 在函数结束后,p的计数器会递减(也可能销毁)
int *x(new int(123)); // x是普通指针
process(x); // 错误,不能将普通指针隐式转换成智能指针。
process(shared_ptr<int>x); // 正确,但是在函数执行完后,x的内存被释放。
int i = *x; // 错误,x指向内容已经被释放
12.1.4 智能指针和异常
void f()
{
shared_ptr<int> sp(new int(123)); // 智能指针分配一个新对象
int *ip = new int(456); // new创建指针
// 中间抛出异常,而且没有捕获,函数直接结束
delete ip; // 被跳过,没有执行,ip没有被释放
} // sp在函数结束时被释放内容,正确
- 默认情况下,shared_ptr 被销毁时,对指针进行delete。
删除器(deleter)函数:用于代替原来delete,即自定义销毁操作。详见std:: shared_ptr::shared_ptr
基本规范:
- 不使用内置指针值初始化(或 reset)多个智能指针。
- 不delete get()返回的指针。
- 不使用 get() 初始化(或 reset)另一个智能指针。
- 记住get()返回的指针,在对应最后的智能指针被销毁时,会失效。
- 如果智能指针管理的资源不是new分配的,要自定义一个删除器。
12.1.5 unique_ptr
- 同shared_ptr,初始化时,必须用直接初始化形式。
- unique_ptr 只占有一个对象,所以不支持普通拷贝或者赋值操作。但可以拷贝或赋值一个将要被销毁的unique_ptr。
unique_ptr<int> p1(new int(123));
unique_ptr<int> p2(p1); // 错误,不支持拷贝
unique_ptr<int> p3;
p3 = p1; // 错误,不支持赋值
// 但可以拷贝或赋值一个将要被销毁的 unique_ptr
unique_ptr<int> p2(p1.release()); // 正确,将p1的指向转给p2。p1为空,p2指向原来p1指向的对象
unique_ptr<int> clone(int value)
{
unique_ptr<int> ret(new int(value));
return ret; // 正确,返回一个局部对象(unique_ptr)的拷贝
}
- release:放弃了指向原来的对象,指针为空值。
- reset:释放了指向对象的内存。
12.1.6 weak_ptr
- 一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象。
auto p = make_shared<int>(123);
weak_ptr<int> wp(p); // wp弱共享p。p的计数器没改变。
if( shared_ptr<int> np = wp.lock() ) // 判断wp指向对象是否为空,不为空成立且获取(通过weak_ptr 获取 shared_ptr)
{
// 这个块区间内,np和p共享对象。
}
12.2 动态数组
12.2.1 new和数组
- 虽然用 new T[ ] 分配的内存称为“动态数组”,但实际上得到的是元素类型的指针,而不是数组类型的对象。
- 因为上面这原因,所以也不能用begin或end,或者是范围for语句。
int *p1 = new int[10]; // 10个为初始化的int
int *p2 = new int[10](); // 10个默认初始化为0的int
int *p3 = new int[10]{ 1, 2, 3, 4 }; // 同数组列表初始化
- 不能在括号中给出初始化器,所以不能用auto分配数组。
- 可以用任意表达式来缺点要分配的对象的数目。
auto ap = new auto[10](1); // 错误,不能在括号内给出初始化器,所以auto也无法判断
int v = 3; // 一个变量
int arr1[v]; // 错误,不能用非常量表达式设置维度
int arr2[0]; // 错误,不能使用0作为维度
int *ip = new int[v]; // 正确,可以用任意表达式分配数组
int *ip = new int[0]; // 正确,但是ip不能解引用,是一个合法的非空指针
delete [] ip; // 释放ip动态数组
- 释放动态数组时,数组中元素按逆序销毁,即最后一个元素先销毁,再到倒数第二个,依此类推。
- unique_ptr 可以管理动态数组。
- C++17前,shared_ptr 不直接支持,需要提供删除器。C++17后支持管理动态数组。见
std::shared_ptr::operator[]
。
unique_ptr<int[]> up(new int[10]);
for (size_t i = 0; i < 10; i++)
up[i] = i;
up.reset(); // 自动调用delete[ ] 销毁其指针
// C++17前,使用shared_ptr,必须提供删除器
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // 使用get获取一个内置指针
sp.reset(); // 使用自定义的删除器
// C++17以后,支持[ ] 下标运算符,可直接使用
shared_ptr<int[]> sp(new int[10]);
for (size_t i = 0; i < 10; i++)
sp[i] = i;
12.2.2 allocator 类
- 提供可以将内存分配和对象构造分开的操作,分配的内存是原始的、未构造的。
- destroy接收一个指针,指向对象执行的析构函数。
allocator<string> alloc;
auto const p = alloc.allocate(n); // 分配n个未初始化的string
auto q = p; // q指向最后构造的元素之后的位置
alloc.construct(q++); // *q为空字符串
alloc.construct(q++, 5, 'c'); // *q为ccccc(使用了初始化器)
alloc.construct(q++, "hi"); // *q为hi
while( q != p )
alloc.destroy(--q); // 销毁掉前面构造的string们
alloc.deallocate(p, n); // 释放掉内存,n与构造时的大小必须相同
- 可以拷贝和填充为初始化内存,见上表。
vector<int> vi{ 1, 2, 3 };
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2); // 分配比vi占用空间大一倍的动态内存:3 * 2 = 6 个int
auto q = uninitialized_copy(vi.begin(), vi.end(), p); // 拷贝vi,从p开始
uninitialized_fill_n(q, vi.size(), 66); // 剩余的填充66
for (size_t i = 0; i < vi.size() * 2; i++) // 结果为 1 2 3 66 66 66
cout << *(p++) << endl;