智能指针(补充)
shared_ptr
支持的操作(unique_ptr也支持的)
函数接口 | 意义 |
---|---|
shared_ptr sp | 空的智能指针,可以指向T类型的对象 |
p | 将p作为一个判断条件,若p指向同一个对象,返回true |
*p | 解引用p,获得指向的对象 |
p->mem | 等价于(*p).mem |
p.get() | 返回p保存的指针,但是要小心使用 |
swap(p,q) | 交换p和q的指针 |
p.swap(q) | 等价于上式子 |
shared_ptr 的独有的操作
接口 | 功能 |
---|---|
make_shared (args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,args初始化这个对象 |
shared_ptr p(q) | p是shared_ptr q的拷贝,会触发引用计数,q中的指针必须能够转化为T* |
p =q | pq都必须是shaared_ptr,且能够互相转化,此操作会递减p的引用计数,++q的引用计数,若p的引用计数降为0,则释放p指针的资源 |
p.unique() | 若p.use_count()==1返回true,否则返回false |
p.use_count() | 返回与p共享的智能指针数量:可能很慢,主要用于调试 |
make_shared函数
最安全的分配和使用动态的方法
示例:
shared_ptr<int> p = make_shared<int>(43);//指向一个值为43的int的shared_ptr
shared_ptr<int> p1 = make_shared<int>();//指向一个初始值为int的。值为0
shared_ptr的拷贝和赋值
shared_ptr<int> p = make_shared<int>(43);
auto q(p);//拷贝
每个shared_ptr都有一个相关联的计数器,称为引用计数
shared_ptr自动销毁所管理的对象,还会自动释放相关联的内存
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动调用 析构函数完成销毁动作,这一特性使得动态内存的管理变得非常容易
使用动态生存期的资源的类
- 程序不知道自己使用了多少对象
- 程序不知道所需对象的具体类型
- 程序需要在多个对象间共享数据
一般而言,如果两个对象共享底层数据,当某个对象被销毁时,我们不能单方面的销毁底层数据
Blob<string> b1;//一个空对象
{
//新的作用域
Blob<string> b2 = {"a","an"};
b1 = b2;//b1 b2共享同一份数据
}//b2被销毁了,但是b2中的元素不能销毁
//b1指向最初b2创建的元素
shared_ptr 和 new的使用
示例
shared_ptr<double> p1;//shared_ptr指向一个double
shared_ptr<int> p2(new int(42));//p2指向一个值为42的int
接受指针参数的智能指针的构造函数时explicit,是不能进行隐式类型的转换,所以不能将一个内置指针的隐式类型转化为一个智能指针,必须使用初始化的形式
shared_ptr<int> p = new int(42); //错误的
shared_ptr<int> p2(new int(42));//正确的
比如实现一个clone函数
shared_ptr<T> clone(T p)
{
// return new T(p); 失败
return shared_ptr<T>(new T(p));//正确
}
定义和改变shared_ptr的方法
方法 | 功能 |
---|---|
shared_ptr p(q) | 拷贝构造,pq共同管理同一份资源 |
shared_ptr p(u) | p从unique_ptr u那里接管了对象的所有权,将u置空 |
shared_ptr p(q,d) | p接管了q所指向对象的所有权,d是析构函数调用的析构方法的对象 |
shared_ptr p(p2,d) | p是shared_ptr p2的拷贝,p将会调用d来替代delete |
p.reset() | 解放p指针,若p是唯一的指针,则会调用析构函数,释放资源 |
p.reset(q) | p释放手里的资源,共同管理q手里的资源 |
p.reset(q,d) | p释放手里的资源,如果p是唯一的资源,就调用d而不是delete释放资源,pq共同管理一份资源 |
p.unique() | p是否是唯一的对象,是返回true,否返回false |
不要混合使用普通指针和智能指针,也不要使用get初始化另一个智能指针或为智能指针赋值
因为智能指针除了作用域就会析构,很容易导致普通指针的非法访问。
智能指针与异常
智能指针保证了即使程序过早的结束,也能确保在内存不需要时将其释放。
void f()
{
shared_ptr<int> sp(new int(10));
//异常检测,且在f中未能捕获
}//在函数结束时,shared_ptr自动释放内存
void f1()
{
int *p = new int(10);
//抛异常,未被捕获
delete p;
}//这里会导致p的资源不会被释放,造成内存泄露
只能指针的陷阱
- 不使用相同的内置指针初始化多个智能指针
- 不delete get() 返回的指针
- 不使用get()初始化或reset另一个智能指针
- 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就无效了
- 如果你使用了智能指针管理指针的资源不是new分配的内存,记住传递给他一个删除器
unique_ptr
unique_ptr某个时刻只能有一个unique_ptr指向某个指定的对象,当unique_ptr被销毁时,指向的对象也会被销毁。
unique_ptr不支持普通的拷贝或者赋值操作
unique_ptr初始化
和shared_ptr一样,但不支持make方法,只能采用直接初始化的方法
unique_ptr<string> p (new string("hello world"));//正确
方法 | 功能 |
---|---|
unique_ptr u1 | 空的unique_ptr,可以指向类型为T的对象,u1会调用delete释放资源 |
unique_ptr<T,D> u2; | 同上,但会调用类型为D的可调用对象来释放资源 |
unique_ptr<T,D> u3(d) | 同上,但会使用d来释放资源 |
u=nullptr | 释放u指向的对象,同时置空 |
u.release() | u释放对指针的控制权,返回指针,同时置空 |
u.reset(q) | 释放u指向的对象,同时u指向这个对象,不写q就置空 |
使用release()
u.release()//错误,会丢失指向某一空间的指着,会导致内存泄露
不能拷贝unique_ptr的规则有一个例外,我们可以拷贝或者赋值一个将要被销毁的unique_ptr,常见的例子就是从函数返回一个unique_ptr
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
//或者
unique_ptr<int> ret(new int(p));
return ret;
}
向unique_ptr传递删除器
//删除器就是仿函数,使用方法:
//1. unique_ptr<T,D> p;
//2. unique_ptr<T,D> p1(d);
weak_ptr
weak_ptr时shared_ptr的补充,解决了循环引用的问题,是一种不控制所指向对象生存期的智能指针,指向由shared_ptr管理的对象。
将一个weak_ptr绑定到shared_ptr就不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁的,对象就会被释放吧,即使有weak_ptr指向对象都会释放,实际上weak_ptr抓住了智能指针的“弱”共享对象的特点。
方法 | 功能 |
---|---|
weak_ptr w | 控指针指向类型为T的对象 |
weak_ptr w(sp) | sp为shared_ptr类型,拷贝 |
w=p | p可以是shared_ptr或者weak_ptr,赋值后w p共享对象 |
w.reset() | 置空 |
w.use_count() | 与w共享对象的shared_ptr的数量 |
w.expired() | use_count() == 0 ? true : false |
w.lock() | expired() == true? 返回一个空的shared_ptr : 指向w的对象的shared_ptr |
举例
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);//wp时弱共享p,p的引用计数未改变
由于对象可能不存在,所以要调用lock()检查weak_ptr指向的对象是否存在
if(shared_ptr<int> np = w.lock())//这里的np是作为一个判断条件,为空就不成立
{
//在if中 np与p共享对象
}
智能指针和动态数组
unique_ptr和动态数组
标准库提供了一个可以管理new分配数组的 unique_ptr 版本,具体使用方法
unique_ptr<int[]> up(new int[10]);//申请一个个数为10的未初始化的int数组
up.release();//自动调用delete[]销毁指针
- 当一个unique_ptr指向一个数组时,不能够使用**. ->**操作符
- unique_ptr指向一个数组时,可以用下标来访问元素
for(int i = 0; i < 10;i++)
{
up[i] = i;
}
指向数组的unique_ptr | 功能 |
---|---|
unique_ptr<T[]> u | u指向一个动态分配的数组,数组类型为T |
unique_ptr<T[]> u§ | u指向内置指针p所指向的动态分配的数组,p必须能够转换为类型T* |
u[i] | 支持下标随机访问 |
shared_ptr和动态数组
shared_ptr不直接支持管理动态数组,需要自己定义删除器。
shared_ptr<int> sp(new int[10],[](int* p){delete[] p;});//lambda表达式
//或者
shared_ptr<int> sp(new int[10],del);//仿函数
void del(int* p)
{
delete[] p;
}
shared_ptr不支持下标运算符号,所以要随机访问就要获取内置指针
//sp[i] 不支持
for(int i = 0;i < 10;i++)
{
*(sp.get() + i) = i;
}
allocator类
new在灵活性上有局限,它将内存分配和对象构造组合在一起。而allocator将内存分配和对象构造分离开来。
它提供类型感知的内存分配方法,它分配最原始的,未构造的内存。
allocator | 功能 |
---|---|
allocator a | 定义对象,可以为类型为T的对象分配空间 |
a.allocator(n) | 分配一段未初始化的原始的,保存n个T对象 |
a.deallocator(p,n) | 释放指针和资源,n是申请的对象个数 |
a.construct(p,args) | p是类型为T*的指针,args是构造p指向内存中对象的构造函数 |
a.destroy§ | 析构函数 |
p是指向最后构造的元素的下一个位置
auto q = p;//p是指向最后构造的元素的下一个位置
alloc.construct(q++);//*q = "";
alloc.construct(q++,"hello");// *q = "hello"
alloc.construct(q++,10,a);//*q = "aaaaaaaaaa"
当我们用完对象后,必须对每个元素调用destroy来销毁他们,函数destroy接受一个指针,对指向的对象执行析构函数。
while(q != p)
{
alloc.destroy(--q);//释放真正构造的string
}
/*
q指向最后构造的元素之后的位置,在调用destroy之前对q进行递减操作。最后一部循环destroy了第一个构造元素,随后q将于p相等,循环结束
*/
释放内存通过deallocate来完成
alloc.deallocate(p,n);
拷贝和填充未初始化内部的算法
方法 | 功能 |
---|---|
uninitialized_copy(b,e,b2) | 迭代器拷贝到b2中,目标是b2 |
uninitialized_copy_n(b,n,b2) | b开始的n个元素拷贝到b2中,目标对象是b2 |
uninitialized_fill(b,e,t) | 迭代器拷贝,数据源来自于t |
uninitialized_fill_n(b,n,t) | b开始创建n个对象,数据源来自于t |
举例n:
auto p = alloc.allocate(v.size()*2);//开这么大的空间
auto q = uninitialized_copy(v.begin(),v.end(),p);//向p中拷贝v,返回最后一个元素的下一个位置的迭代器
uninitialized_fill_n(q,v.size(),111);//向p以后的n个位置初始化为111