C++笔记(2) —— 动态内存
简述
静态内存用来保存局部static对象、类static数据成员、和函数之外的变量,而栈内存用来保存函数内的非static对象。这两部分内存都是经由编译器自动创建和销毁的,而除此之外还有一个内存池,一般被称为堆,是用来存储动态分配的对象。这些对象需要交由程序自己控制生存期。
直接分配
malloc和free是C语言的库函数,在栈中动态分配内存,而在C++里面,定义了两个运算符 new/delete 来分配和释放内存。它们可以通过被重载而实现不同于默认分配的新的分配方式,
- 分配 new:分配一个值,直接使用 new + 类型即可,返回的是该类型的指针。而如果分配数组,则是 new + 类型[数组大小],返回的是指向第一个元素的指针。
int* pi = new int; //直接分配未初始化的对象指针
const int* p = new const int(1024); //使用直接初始化方式分配
int* pArray = new int[10]; //分配数组
- 释放 delete:在动态内存使用过后,需要释放空间,此时需要通过delete来释放。如果释放一个对象,使用 delete + 对象名称。如果释放一个数组,则需要delete[] + 数组名称,一定要记得 [],否则会造成内存泄漏。同时,一个空间被释放后,指针指向的区域就是无效的了,所以在delete之后还需要将原来的指针设为nullptr,否则将会产生空悬指针(dangling pointer)。(当然即使是这样也并不是就完全安全了,如果还有另外的指针指向同一个地址,那么如果不把这个指针设为nullptr,依然会产生问题)
delete pi;
delete p;
delete[] pArray;
pi = nullptr;
p = nullptr;
pArray = nullptr;
智能指针
因为直接分配动态内存十分容易引发问题,所以在c++11标准中为了更容易和更安全的使用动态内存,提供了几种智能指针来方便我们管理动态对象。智能指针与指针的行为很类似,而最重要的区别就在于自动释放对象。智能指针也是一个模板类,在创建的时候同样需要提供指针指向的类型。
-
shared_ptr
shared_ptr允许多个指针指向同一个对象,使用方式与普通指针类似,可以认为shared_ptr有一个引用计数,通过该计数来自动决定何时销毁。
shared_ptr<string> p1;
if (p1 && p1->empty()) *p1 = "hi"; //*操作,->操作,指针判断都可以使用
shared_ptr<int> p2(new int(33)); //通过直接初始化形式初始化指针
更安全使用shared_ptr来进行动态内存分配的方式是调用make_shared函数。make_shared函数在动态内存中分配一个对象并初始化,返回指向该对象的shared_ptr:
shared_ptr<int> p2 = make_shared<int>(22);
auto p3 = make_shared<string>("hello");
每当进行一次赋值或者拷贝操作时,shared_ptr都会递增自己的计数器,而每当自身离开某个作用域或者被重新赋值而应被清除时,递减自己的计数器。当销毁计数为最后一个时,shared_ptr就会自动调用析构函数销毁自身,同时释放内存。正是通过这个计数器,shared_ptr实现了智能指针的更安全使用动态内存的方法。
-
unique_ptr
unique_ptr与shared_ptr不同,同一时间只能有一个unique_ptr指向一个给出的对象,当其被销毁时,指向的对象也被销毁。定义unique_ptr时,需要绑定到一个new返回的指针上,所以初始化unique_ptr必须采用直接初始化形式,而且因为只能有一个unique_ptr指向给定的对象,所以不支持拷贝和赋值操作。
unique_ptr<int> p(new int(10));
虽然不能拷贝和赋值,但可以通过release或reset将所有权转移,release操作返回保存的指针并清空自己:
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("test"));
p2.reset(p3.release());
对于动态数组,也可以使用unique_ptr来管理:
unique_ptr<int[]> up(new int[10]);
up.release(); //release操作也会自动调用delete[]来销毁指向的数组
-
weak_ptr
weak_ptr指向由shared_ptr指向的对象,但其不控制对象的生命周期,绑定weak_ptr到shared_ptr不会引起引用计数的改变。而如果shared_ptr指向的最后一个对象被销毁引起shared_ptr被销毁,对象就会被释放,而不会理会是否还有weak_ptr指向该对象,所以weak_ptr就是一种弱共享对象。
shared_ptr<int> p = make_shared<int>(10);
weak_ptr<int> p2(p);
因为weak_ptr指向的对象可能已经不存在,所以不能直接访问对象,而需要通过调用lock之后,返回的shared_ptr来访问。
if (shared_ptr<int> np = p2.lock())
{
//weak_ptr指向的对象存在,并通过np与对象共享
}