12.1.1 动态内存与智能指针
全局对象:在程序启动时分配,程序结束时销毁
局部对象:进入其作用域时被创建,离开作用域销毁(栈对象)
静态对象:在第一次使用之前分配,程序结束销毁
动态内存和智能指针
动态内存(堆)的管理是通过一对运算符来完成的
new:在动态内存中为对象分配空间并返回一个指向该对象的指针
delete:接受一个动态内存的指针,销毁该对象,释放相关内存
为了更安全的使用内存,C++11提供了两个智能指针
shared_ptr:允许多个指针指向同一个对象
unique_ptr:独占对象
和普通指针类似,区别在于它负责自动释放所指向的对象
#include<list>#include<string>#include<memory>//智能指针也是模板,默认初始化的智能指针中保存着一个空指针 std::shared_ptr<std::string> p1;//可指向string std::shared_ptr<std::list<int>> p2; //可指向 指向int的list(即std::list<int>) //如果p1不为空,检查它是否可以指向一个空字符串 if (p1&&p1->empty()){ *p1 ="HI";//解引用p1,将HI赋予其指向的对象 }
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数
//指向一个值为42的int shared_ptr std::shared_ptr<int> p3 = std::make_shared<int>(42); //指向一个值为 "9999999999" 的string std::shared_ptr<std::string> p4 = std::make_shared<std::string>(10,'9'); //指向一个值初始化的int,即值为0 std::shared_ptr<int> p5 = std::make_shared<int>(); //指向一个动态分配的空std::vector<std::string> auto p6 = std::make_shared<std::vector<std::string>>();
auto r = std::make_shared<int>(42);//r指向的int只有一个引用者 r = q;//给r赋值,令它指向另一个地址 //递增q的引用计数,递减r原来的引用计数,r原来指向的对象没有引用者,会自动释放
//factory返回一个shared_ptr,指向一个动态分配内存的对象 std::shared_ptr<FOO> factory(T arg){ return std::shared_ptr<FOO>(arg); } void use_factory(T arg){ std::shared_ptr<FOO> p = factory(arg); }//离开作用域时, arg对象的内存将被自动释放
使用动态内存的一个常见原因:多个对象共享相同的状态
12.1.2 shared_ptr 和 new 结合使用
直接管理内存
默认初始化
如果定义变量没有被初始化,则变量被赋予默认值
默认值由变量类型决定,且和变量定义位置有关
内置类型,函数体之外被初始化为 0
每个类决定其初始化对象的方式(string 空字符串)
int* pi = new int; //指向一个动态分配的,未初始化的无名对象 string* ps= new string; //初始化空string
直接初始化
string* ps1 = new string; //默认初始化空string string* ps2 = new string(); //值初始化为空string int* pi1 = new int; //默认初始化: 其值未定义 int* pi2 = new int(); //值初始化为 0
使用 auto 从初始化器来推断分配的对象类型
auto p1 = new auto(obj); //p1指向一个与 obj 类型相同的对象,该对象使用 obj 初始化 auto p1 = new auto(a,b,c); //错误:括号中只能有单个初始化器
用 new 分配 const 对象是合法的
//分配并初始化一个 const int const int* pci = new const int(1024); //分配并默认初始化一个 const 的空 string const string *pcs = new const string;
内存耗尽
int* p1 = new int; //分配失败,则new 抛出std::bad_alloc int* p1 = new(nothrow); //分配失败,则返回一个空指针 //定位 new 表达式,nothrow 对象告诉它不能抛出异常
指针和 delete
int i, *pil = &i, *pi2 = nullptr; double *pd = new double(33),*pd2 = pd; delete i; //错误 delete pil; //错误,非动态内存的指针(没有new也不需要delete) delete pd; //正确 delete pd2; //错误,pd2指向的内存已经被释放了 delete pi2; //正确释放一个空指针没有错 const int* pci = new const int(1024); delete pci; //正确,释放一个const对象
在 delete 之后,指针就变成了空悬指针
int *p(new int(42)); //p指向动态内存 auto q = p; //p 和 q 指向相同的内存 delete p; //p 和 q 均变为无效 p = nullptr; //p不再绑定到任何对象 //q依赖是空悬指针
shared_ptr 和 new 结合使用
std::shared_ptr<double> p1; std::shared_ptr<int> p2(new int(42));
智能指针的构造函数是 explicit 的
std::shared_ptr<int> p1 = new int(1024); //错误,必须直接初始化 std::shared_ptr<int> p2(new int(42)); //正确 std::shared_ptr<int> clone(int p){ return std::shared_ptr<int>(new int(p)) }
不要混合使用普通指针和智能指针
因为普通指针指向的对象将会被智能指针销毁时释放
std::shared_ptr<int> p (new int(42)); //引用计数为1 process(p); //拷贝p会递增它的引用计数,在process中引用计数为2 int i = *p; //正确,引用计数为1 //用内置指针显式构造一个shared_ptr,这样做会导致错误 int* x(new int(1024)); //x是一个普通指针 process(x); //错误,不能将 int* 转换为 shared_ptr<int> process(std::shared_ptr<int>(x)); //合法,但是内存将会被释放 int j = *x; //j未定义,因为 x 指向的内存已经被释放,x是空悬指针
get : 向不能使用智能指针的代码,传递一个内置指针
永远不要用 get 初始化一个智能指针或为另一个智能指针赋值
std::shared_ptr<int> p(new int(42)); //引用计数为1 int* q = p.get(); //正确,但使用q的时候要注意,不要让其管理的指针释放 { //新程序块 //未定义:两个独立的shared_ptr指向相同的内存 std::shared_ptr<int>(q); } int foo = *p //未定义的,p指向的内存已经被释放了
reset:更新引用计数,会释放 p 指向的对象
std::shared_ptr<int> p = new int(42); //错误,不能将一个指针赋予 shared_ptr std::shared_ptr<int> p; p.reset(new int(42)); //正确,p指向一个新对象 //------------------------------------------------- std::shared_ptr<std::string> p; if (!p.unique()){ //p.use_count为1,返回true,否则返回false p.reset(new std::string(*p)); //不是唯一使用者,分配新的拷贝给智能指针 } *p += newVal; //是唯一对象的使用者,可直接改变对象的值
智能指针和异常
/* 使用智能指针,即使程序块过早结束,也能正确释放内存 */ void f(){ std::shared_ptr<int> sp(new int(42)); //分配一个新对象 //这段代码抛出一个异常,且 f()函数未捕获 }//在函数结束时,智能指针会自动释放内存 void f(){ int *ip = new int(42); //动态分配对象 //这段代码抛出一个异常,且 f()函数未捕获 delete ip; //在退出前释放内存,这段释放内存的操作并未被执行 }
使用类似的技术来管理不具有良好定义的析构函数的类