C++中 内存划分:
- 静态内存:保存局部static对象、类static数据成员、以及定义在任何函数之外的变量;
- 栈内存:保存定义在函数内的非static对象;
- 自由空间(堆):存储动态分配的对象,当动态对象不再使用时,需要显示的销毁;
程序使用动态内存的原因:
- 不知道自己需要使用多少对象;
- 不知道所需对象的准确类型;
- 需要在多个对象间共享数据;
智能指针
智能指针的行为类似于常规指针,负责自动释放所指向的对象;智能指针也是模板,默认初始化的智能指针里面保存着一个空指针;
智能指针模板类:定义在memory头文件中
- shared_ptr:允许多个指针指向同一个对象;
- unique_ptr:独占所指的对象;
- weak_ptr:是一种弱引用,指向shared_ptr所管理的对象;
shared_ptr 和 unique_ptr 共有的一些操作:
*p //解引用,获得它指向的对象
p.get() //返回p中保存的指针
swap(p, q) //交换p和q中的指针
p.swap(q)
关于正确使用智能指针的一些规范:
- 不使用相同的内置指针值初始化或reset多个智能指针。
- 不delete get()返回的指针。
- 不使用get()初始化或reset另一个智能指针。
- 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,指针就会变的无效了。
- 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。
shared_ptr
独有的一些重要的操作:
make_shared<T>(args) //返回一个shared_ptr,指向动态分配的类型为T的对象,使用args初始化
shared_ptr<T>p(q) //拷贝
p.unique() //返回bool,判断是否有共享的指针数量
p.use_count() //返回与p共享的智能指针数量,主要用于调试
shared_ptr进行拷贝之后都指向同一个对象,会有一个引用计数,一旦计数器变为0,就会调用析构函数,自动释放自己管理的对象;
保证shared_ptr在无用之后不再保留非常重要,其在无用之后仍然保留的一种可能情况是,将shared_ptr放在了一个容器中,然后重排了容器,从而不需要某些元素;如果将shared_ptr存放在一个容器中,而后不需要全部的元素而只需要用其中一部分,需要用eraser删除不需要的那些元素;
直接管理内存
C++使用 new 和 delete 分配和释放内存;
new
new无法为其分配的对象命名,而是返回一个指向该对象的指针:
int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象
//不指定初始化的值,则对象都会通过默认构造函数来初始化
分配const对象时:
const int *pci = new const int(1024); //分配并初始化一个const int
一个动态分配的const对象一定要进行初始化。对于一个定义了默认构造函数的类可以隐式的初始化,其他的类型的对象必须显示初始化。
delete
传递给delete的指针必须指向动态分配的内存,或者一个空指针;
编译器不能分辨一个指针是静态还是动态分配的对象。编译器也不能分辨一个指针指向的的内存是否已经被释放了。
由内置指针管理的动态内存被显示的释放之前一直会存在。
关于空悬指针的问题:
当我们delete一个指针之后,指针值就变得无效了,但有时指针仍然保存着(已经释放了的)动态内存的地址。所以我们需要在指针离开其作用域之前释放掉它关联的内存。如果我们需要继续使用此指针,可以delete之后赋予nullptr。
关于多个指针指向相同内存的问题:
delete其中一个指针后,其他的指针需要置空。
int *p(new int(42)); //p指向动态内存
auto q = p; //p和q指向相同的内存
delete p; //q和p均无效
p = nullptr; //指出p不再绑定到任何对象
shared_ptr 和 new 的结合使用
接受指针参数的智能指针构造函数是explicit的,也就是只能通过直接初始化,不能拷贝初始化。不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化的形式。
shared_ptr<int> p1 = new int(11); //错误
shared_ptr<int> p2(new int(42)); //正确
当使用shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr,这样做了之后就不应该再使用内置指针来访问shared_ptr指向的内存了。
不要混合使用智能指针和内置指针,也不要使用get初始化另一个智能指针或为另一个智能指针赋值。get():返回智能指针管理对象的一个内置指针。
unique_ptr
与shared_ptr相似,需要将其绑定到一个new返回的指针上,初始化unique_ptr必须使用直接初始化的形式,与之不同的是它不支持拷贝,不支持赋值。
unique_ptr 一些常用的操作:
u.release() //u放弃对指针的控制,返回指针,并将u置空
u.reset() //释放u指向的对象
u.reset(p) //释放原来的,再指向p
weak_ptr
weak_ptr不控制所指向对象生存期的智能指针,将一个weak_ptr绑定到shared_ptr上不会改变shared_ptr的引用计数。
weak_ptr一些常用的操作:
w.reset() //将w置空
w.use_count() //与w共享对象的shared_ptr的数量
w.expired() //w.use_count()为0,返回true,否则返回false
w.lock() //如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。weak_ptr不能影响对象,但可以检查对象在不在。