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

文章详细介绍了C++中的几种智能指针,包括shared_ptr的拷贝和引用计数管理,unique_ptr的独占所有权,以及weak_ptr如何解决循环引用问题。此外,还讨论了智能指针与动态数组的交互,以及allocator类在内存管理和对象构造中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

智能指针(补充)

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 =qpq都必须是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=pp可以是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[]> uu指向一个动态分配的数组,数组类型为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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值