12.1.3 unique_ptr 和 weak_ptr
unique_ptr : 独自拥有所指向的对象
std::unique_ptr<double> p1; //可以指向一个double的unique_ptr std::unique_ptr<int> p2(new int(42)); //p2指向一个值为42的int std::unique_ptr<std::string> p1(new std::string("HI")); std::unique_ptr<std::string> p2(p1); //错误:unique_ptr不支持拷贝 std::unique_ptr<std::string> p3 = p1; //错误:unique_ptr不支持拷贝
unique_ptr 虽然不能拷贝或赋值 ,但可以通过 release 或 reset 将指针所有权从一个(非const)的转移到另一个
std::unique_ptr<std::string> p1(new std::string("HI")); std::unique_ptr<std::string> p2(p1.release()); //release将p1置为空 std::unique_ptr<std::string> p3(new std::string("HI1")); //将所有权从p3转移到p2 p2.reset(p3.release()); //释放原来指向的内存
不能拷贝的例外 : 可以拷贝或赋值一个将要销毁的 unique_ptr
std::unique_ptr<int> clone(int p){ //正确: 从int* 创建一个 unique_ptr return std::unique_ptr<int>(new int(p)); } std::unique_ptr<int> clone(int p){ std::unique_ptr<int> ret(new int(p)); return ret; }
重写默认删除器
动态内存与智能指针
weak_ptr :绑定到 shared_ptr ,不会改变引用计数
auto p = make_shared<int>(42); weak_ptr<int> wp(p); //wp弱共享p,p的引用计数并未改变 //不能使用 weak_ptr 直接访问对象,必须调用 lock if(shared_ptr<int> np = wp.lock()){ //如果np不为空 //在if中, np 和 p 共享对象 }
展示 weak_ptr 用途
检查指针类:将为StrBlob 类定义一个伴随指针类
//对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常 class StrBlobPtr{ public: StrBlobPtr():curr(0){}; StrBlobPtr(StrBlob & a,size_t sz =0):wptr(a.data),curr(sz){}; string& deref() const; StrBlobPtr& incr(); //前缀递增 private: //若检查成功,check返回一个指向 vector 的 shared_ptr shared_ptr<vector<string>> check(size_t,const string&) const; //保存一个 weak_ptr,因为底层 vector 可能会被销毁 weak_ptr<vector<string>> wptr; size_t curr;//在数组当前的位置 } shared_ptr<vector<string>> StrBlobPtr::check(size_t i,const string& msg) const{ auto ret = wptr.lock(); //vector还存在吗? if(!ret) throw runtime_err("unbound StrBlobPtr"); if(i>= ret->size()) throw out_of_range(msg); return ret; //否则,返回指向vector的shared_ptr<vector<string>> } string& StrBlobPtr::deref() const{ auto p = check(curr,"derefernce past end"); return (*p)(curr); //*p是对象所指的vector } StrBlobPtr& StrBlobPtr::incr(){ //如果curr已经指向容器的尾后,就不能递增了 check(curr,"increment past end of StrBlobPtr"); ++curr; return *this; } //对于友元声明来说,前置声明是必要的 class StrBlobPtr; class StrBlob{ friend class StrBlobPtr; //... //返回指向首元素和尾后元素的StrBlobPtr StrBlobPtr begin(){return StrBlobPtr(*this)}; StrBlobPtr end(){ auto ret = StrBlobPtr(*this,data->size()); return ret; }; };
weak_ptr 打断 shared_ptr 的循环引用
std::weak_ptr :打断 std::shared_ptr 所管理的对象组成的环状引用。(打破shared_ptr的循环引用)
若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。
能令环中的指针之一为弱指针以避免此情况
#include <iostream>#include <memory>using namespace std; class Parent; typedef std::shared_ptr<Parent> ParentPtr; class Child { public: ParentPtr father; Child() { cout << "hello Child" << endl; } ~Child() { cout << "bye Child\n"; } }; typedef std::shared_ptr<Child> ChildPtr; class Parent { public: ChildPtr son; Parent() { cout << "hello parent\n"; } ~Parent() { cout << "bye Parent\n"; } }; void testParentAndChild() { ParentPtr p(new Parent()); ChildPtr c(new Child()); p->son = c; c->father = p; } int main() { testParentAndChild(); return 0; }
一旦外部指向shared_ptr资源失效,那么weak_ptr管理的资源自动失效
#include <iostream>#include <cassert>#include <memory>using namespace std; class Parent; typedef std::shared_ptr<Parent> ParentPtr; typedef std::weak_ptr<Parent> WeakParentPtr; class Child { public: WeakParentPtr father; // 只要一环换成 weak_ptr, 即可打破环 Child() { cout << "hello Child" << endl; } ~Child() { cout << "bye Child\n"; } }; typedef std::shared_ptr<Child> ChildPtr; typedef std::weak_ptr<Child> WeakChildPtr; class Parent { public: ChildPtr son; Parent() { cout << "hello parent\n"; } ~Parent() { cout << "bye Parent\n"; } }; void testParentAndChild() { ParentPtr p(new Parent()); ChildPtr c(new Child()); p->son = c; c->father = p; cout << (c->father).use_count() << endl; cout << (p->son).use_count() << endl; } int main() { testParentAndChild(); return 0; }
12.2 动态数组
new 和数组
动态数组并不是数组类型,分配一个数组将会得到一个元素类型的指针
//通过get_size确定分配多少个int int* pia = new int[get_size()]; //pia指向第一个int //方括号中的大小必须是整数,但不必是常量 typedef int arrT[42]; //arrT表示42个int的数组类型 int *p = new arrT; //分配一个 42个int的数组,p 指向第一个int //编译器将会执行 int *p = new int[42];
初始化动态分配对象的数组
int* pia1 = new int[10]; //10个未初始化的int int* pia2 = new int[10](); //10个初始化为0的int string* psa1 = new string[10]; //10个空字符串 string* psa2 = new string[10](); //10个空字符串 //3个int分别用列表中对应的初始化器初始化 int* pia3 = new int[3]{0,1,2}; //前4个初始化器初始化,剩余进行值初始化 string* psa3 = new string[10]{"A","B","C",string(3,'x')};
动态分配一个空数组是合法的
size_t n = get_size(); //get_size返回需要的元素数目,可以为0 int* p = new int[n]; //分配数组保存元素 for(int *q=p;q!=p+n;++q){ //处理数组 } char arr[0];//错误,不能定义长度为0的数组 char cp= new char[0]; //正确,但cp不能解引用
释放动态数组
delete p; //p必须指向一个动态分配的对象,或者为空 delete p[]; //pa必须指向一个动态分配的数组或为空 //数组中的元素按逆序销毁
智能指针和动态数组
//标准库提供了一个可以管理new分配数组的unique_ptr版本 unique_ptr<int[]> up(new int[10]); up.release(); //自动销毁其管理的指针 for(size_t i=0;i!=10;++i){ up[i] = i; //为每个元素赋值 }
shared_ptr 不直接支持管理动态数组
//如果希望使用 shared_ptr 管理,必须自定义删除器 shared_ptr<int> sp(new int[],[](int *p){delete[] p;}); sp.reset(); //使用提供的 lambda 释放数组,它使用 delete[] //shared_ptr未定义下标运算符,并且不支持指针的算术运算 for(size_t i=0; i!=10; ++i){ *(sp.get() +i) = i; //使用 get 获取一个内置指针 }
Allocator 类
将内存分配和对象构造分离
分配大块内存,但只在真正需要时才执行指向对象创建操作
//将内存分配和对象构造组合在一起可能导致不必要的浪费 string* const p = new string[n]; //构造 n 个空 string string s; string *q = p; //q指向第一个string while( cin>>s && q != p+n ){ *q++= s; //赋予*q一个新值 } const size_t size = q-p; //记录读取多少个string //使用数组 delete[] p;
1、可能不需要 n 个 string
2、每个对象都赋予了两遍值
3、没有默认构造函数的类就不能动态分配数组了
Allocator 类分配的内存是原始的,未构造的
allcator<string> alloc; //可分配 string 的 allocator 对象 //分配一段原始的,未构造的内存 auto const p = alloc.allocate(n); //分配 n 个未初始化的string auto q = p; //q指向最后构造的元素之后的位置 //在q指向的内存中构造一个对象 alloc.construct(q++); alloc.construct(q++,10,'c'); alloc.construct(q++,"HI"); cout << *p << endl; //正确,使用string的输出运算符 cout << *q << endl; //错误,q指向未构造的内存 while(q != p){ alloc.destroy(--q); //释放构造的string //只能对真正构造了的元素进行destroy操作 } //先释放对象,再释放内存 alloc.deallocated(p,n);释放内存
标准库为 allocator 提供了两个伴随算法,可以在未初始化内存中创建对象
假定有一个 int 的 vector ,希望将其拷贝到动态内存中
//分别比 vi 中元素所占空间大一倍的动态内存 auto p = alloc.allocate(v.size()*2); //通过拷贝 vi 中的元素来构造从 p 开始的元素 auto q = uninitialized_copy(v.begin(),v.end(),p); //前两个参数表示输入序列,第三个参数为目的地空间 //与copy不同,uninitialized_copy在给定的位置上构造元素 //q指向最后一个构造的元素之后的位置 //将剩余的元素初始化为42 uninitialized_fill_n(q,v.size(),42);