C++ 学习笔记(12)动态内存、智能指针、new和delete、动态数组、allocator
参考书籍:《C++ Primer 5th》
API:动态内存管理
- 静态内存:
- 保存局部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 = "hi";
auto p = make_shared<int>(12);
auto q(p);
auto r = make_shared<int>(12);
r = q;
- shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,其析构函数就会销毁对象,释放内存。
void function()
{
shared_ptr p =make_shared(666);
return p;
}
- 如果将shared_ptr存放于容器中,对于不需要用的元素,记得要使用erase删除,这样才能释放内存。
12.1.2 直接管理内存
int *p1 = new int;
int *p2 = new int();
auto p3 = new auto(1);
auto p4 = new int{ 1 };
auto p5 = new auto{ 1 };
- delete:释放动态分配的内存,也可以是空指针。释放非new分配的内存或者同一指针释放多次的行为是未定义的。
- 在指针被delete之后,会变成空悬指针(dangling pointer),即指向一块已经失效的内存的指针。可以将指针赋予nullptr。
12.1.3 shared_ptr 和 new 结合使用
- 接收指针参数的智能指针构造函数是explicit的,即不能隐式将内置指针转换成智能指针,必须使用直接初始化形式初始化一个智能指针。
shared_ptr p1 = new int(123);
shared_ptr p2(new int(123));
if (!p2.unique())
p2.reset(new int(123));
- 不要混合使用普通指针和智能指针。
- 不要用get初始化另一个智能指针或赋值。
- get返回普通指针,指向智能指针管理的对象。不能delete这个指针。
void process(shared_ptr<int> p) {}
int *x(new int(123));
process(x);
process(shared_ptr<int>x);
int i = *x;
12.1.4 智能指针和异常
void f()
{
shared_ptr<int> sp(new int(123));
int *ip = new int(456);
delete ip;
}
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<int> p2(p1.release());
unique_ptr<int> clone(int value)
{
unique_ptr<int> ret(new int(value));
return ret;
}
- release:放弃了指向原来的对象,指针为空值。
- reset:释放了指向对象的内存。
12.1.6 weak_ptr
- 一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象。
auto p = make_shared<int>(123);
weak_ptr<int> wp(p);
if( shared_ptr<int> np = wp.lock() )
{
}
12.2 动态数组
12.2.1 new和数组
- 虽然用 new T[ ] 分配的内存称为“动态数组”,但实际上得到的是元素类型的指针,而不是数组类型的对象。
- 因为上面这原因,所以也不能用begin或end,或者是范围for语句。
int *p1 = new int[10];
int *p2 = new int[10]();
int *p3 = new int[10]{ 1, 2, 3, 4 };
- 不能在括号中给出初始化器,所以不能用auto分配数组。
- 可以用任意表达式来缺点要分配的对象的数目。
auto ap = new auto[10](1);
int v = 3;
int arr1[v];
int arr2[0];
int *ip = new int[v];
int *ip = new int[0];
delete [] 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();
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i;
sp.reset();
12.2.2 allocator 类
- 提供可以将内存分配和对象构造分开的操作,分配的内存是原始的、未构造的。
- destroy接收一个指针,指向对象执行的析构函数。
allocator<string> alloc;
auto const p = alloc.allocate(n);
auto q = p;
alloc.construct(q++);
alloc.construct(q++, 5, 'c');
alloc.construct(q++, "hi");
while( q != p )
alloc.destroy(--q);
alloc.deallocate(p, n);
vector<int> vi{ 1, 2, 3 };
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2);
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
uninitialized_fill_n(q, vi.size(), 66);
for (size_t i = 0; i 2; i++)
cout <<*(p++) <