> 文章列表 > 【C++】智能指针(补充)

【C++】智能指针(补充)

【C++】智能指针(补充)

智能指针(补充)

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