前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++智能指针详解(共享指针,唯一指针,自动指针)

C++智能指针详解(共享指针,唯一指针,自动指针)

作者头像
Gabriel
发布2022-11-15 14:25:06
1.5K0
发布2022-11-15 14:25:06
举报
文章被收录于专栏:C/C++C/C++

前言:智能指针在C++11中引入,分为三类:

  1. shared_ptr:共享指针
  2. unique_ptr:唯一指针
  3. auto_ptr:自动指针

一、共享指针

几个共享指针可以指向同一个对象; 每当shared_ptr的最后一个所有者被销毁时,关联对象或关联资源就会被删除

1. 创建方式:
代码语言:javascript
复制
shared_ptr<string> pPoint{new string("pointer")};
代码语言:javascript
复制
shared_ptr<string> pPoint;
pPoint.reset(new string("point"));
代码语言:javascript
复制
shared_ptr<string> pPoint = make_shared<string>("point");
2. 使用:

UNIT1:引用

代码语言:javascript
复制
shared_ptr<string> pPoint1 = make_shared<string>("point1");
shared_ptr<string> pPoint2 = make_shared<string>("point2");

// *point:解引用;
(*pPoint1)[0] = 'P'; //point1 -> Point1

// point->API:解引用并调用其成员函数;
pPoint2->replace(0, 1, 'P'); //point2 -> Point2

// 向容器中插入point,增加一次引用
vector< shared_ptr<string>> vtPoint;
vtPoint.push_back(pPoint1);
vtPoint.push_back(pPoint2);
vtPoint.push_back(pPoint1); //这里*pPoint1虽然被插入了两遍,但是指向的还是同一个对象
*pPoint1 = "pPoint3"; //此时vtPoint中为pPoint3,pPoint2,pPoint3

这里智能指针用到的是一种引用技术:当一个对象被引用了多次时,那么这个对象的引用基数就会变大;当销毁引用这个对象的智能指针时,这个对象的引用基数就会变小;当引用基数变为0时,那么这个被引用对象就会被销毁,不会产生内存泄露的风险

UNIT2:比较运算符

代码语言:javascript
复制
shared_ptr<int> point1 = make_shared<int>(1);
shared_ptr<int> point2 = make_shared<int>(2);
shared_ptr<int> point3;
shared_ptr<double> point4 = make_shared<double>(1);

bool p1 = point3 == point3;
bool p2 = point1 < point2;
bool p3 = point1 > point3;
bool p4 = point1 == point4; //compile error

同类型的共享指针才能使用共享指针

UNIT3:强制类型转换

代码语言:javascript
复制
share_ptr<void> point(new int(1)); //共享指针内部保存void型指针
share_ptr<int> point(static_cast<int *>(point.get())); //compile error,undefined pointer
static_pointer_cast<int *>(point);

共享指针强制转换运算符允许将其中包含的指针强制转换为其他类型指针; 只能使用智能指针特定的强制转换运算符:

  • static_pointer_cast
  • dynamic_pointer_cast
  • const_pointer_cast

UNIT4:线程安全 共享指针不是线程安全的; C++标准库提供了针对共享指针的原子接口; 针对共享指针本身的操作是原子的,并不包含该指针引用的具体值

代码语言:javascript
复制
atomic_is_lock_free(&point) //如果point的原子接口是没上锁的,那么返回true
atomic_load(&point) //返回point
atomic_store(&point1, point2) //使用point2对point进行赋值
atomic_exchange(&point1, point2) //交换point1和point2的值

注: 多个共享指针不能拥有同一对象,否则会出现段错误 可使用enable_shared_from_this和share_from_this生成共享指针

3. 销毁:

定义删除器,例:point(…,D) 删除器可以是普通函数、匿名函数、函数指针等符合签名要求的可调用的对象 只有最后引用对象的共享指针销毁时才会销毁对象

代码语言:javascript
复制
void delFunc(string *p)
{    
    cout << "Func del " << *p << endl;
    delete p;
}
cout << "begin" << endl;
shared_ptr<string> pPoint;
{
    shared_ptr<string> pPoint1(new string("point1"),
                        [](string* p) {
                            cout << “ delete " << *p << endl;
                            delete p;
                        });
    pPoint1 = pPoint2;
    shared_ptr<string> pPoint2(new string("point2"), delFunc);
}
cout << "end" << endl;

注: 不能为数组创建一个共享指针 共享指针提供的默认删除程序将调用delete,而不是delete [] 可使用自定义的删除器,删除器中使用delete[] 可使用default_delete作删除器,因为它使用delete []

附:弱指针(weak_ptr)
  • 弱指针是共享指针辅助类,其允许共享但不拥有对象,因此不会增加关联对象的引用次数
  • 不能使用运算符*和->直接访问弱指针的引用对象,而是使用lock函数生成关联对象的共享指针(可能为空)
  • 当拥有该对象的最后一个共享指针失去其所有权时,任何弱指针都会自动变为空

二、唯一指针

指针唯一性; 继承了自动指针auto_ptr,更不易出错; 抛出异常时可最大限度避免资源泄漏

1. 手动释放资源存在的问题:

问题代码示例:

代码语言:javascript
复制
void func1()
{
    ClassA* ptr = new ClassA; //Create an objects manually
    ... //Perform some operations
    //delete ptr; //Cleanup(Manually destroy objects)
}
void func2()
{
    ClassA* ptr = new ClassA; //Create an objects manually
    try {
        ... //Perform some operations
    } catch (...) { //Handle exception
        delete ptr; //Cleanup
        throw; //Rethrow the exception
    }
    delete ptr; //Clean up on normal exit
}

问题综述: func1中,忘记释放资源导致资源泄露; func2中,在释放资源如果发生异常导致资源泄露; func2中,使用异常捕获的方法会随着资源数量和异常类型的增加导致代码变得复杂

唯一指针代码示例:

代码语言:javascript
复制
void func()
{
    //Create and initialize a unique_ptr pointer
    unique<ClassA> ptr(new ClassA);
    ... //Perform some operations
}

唯一指针可以解决func1和func2函数中资源释放的问题。

2. 使用
代码语言:javascript
复制
//创建唯一指针
unique_ptr<string> uq(new string("Point"));
(*uq)[0] = 'A'; //替换第一个字符
uq->append("one"); //追加字符串
cout << *uq << endl; //打印uq字符串
代码语言:javascript
复制
//空的唯一指针
unique_ptr<string> uq;
uq = nullptr;
uq.reset();
代码语言:javascript
复制
unique_ptr<string> uq(new string("Point"));
string* str = uq.release(); //uq释放,失去唯一性

注: 唯一指针定义了*、->运算符,没有定义类似++的指针算法 唯一指针不可使用赋值语法进行初始化,应使用普通指针初始化 唯一指针可以为空 release()可以让唯一指针返回其拥有的对象,并失去指向该对象的唯一性,调用release()的指针将指向返回的对象

3. 检查唯一指针是否拥有对象的三种方法:
代码语言:javascript
复制
//调用操作符bool()
if (uq) //如果uq不为空
{ 
    cout << *uq << endl;
}
代码语言:javascript
复制
//与nullptr进行比较
if (uq != nullptr) //如果uq不为空
代码语言:javascript
复制
//check unique_ptr中的原始指针是否为空
if (uq.get() != nullptr) //如果uq不为空
4. 指针对象唯一性的转移
代码语言:javascript
复制
//形参uq获得对象的所有权
void sink(unique_ptr<ClassA> uq) 
{
    ...
}
unique_ptr<ClassA> uq(new ClassA);
sink(move(uq)); //uq失去关联对象的所有权

unique_ptr<ClassA> source()
{
    unique_ptr<ClassA> uq(new ClassA);
    ...
    return uq; //将uq关联对象的所有权转移给调用函数
}
uq = source();

要将新值赋给唯一指针,该新值必须是唯一指针 函数可以作为数据的接收端,也可以作为数据发送源 return语句不需要move()的原因是C++11规定编译器将自动尝试移动

5.使用唯一指针处理数组
代码语言:javascript
复制
unique_ptr<string[]> uq(new string[666]); 
cout << uq[0] << endl;

针对数组的接口不提供运算符*->,而提供运算符[] 针对数组的销毁提供了特殊处理 若唯一指针失去对象所有权,则对其拥有的对象调用delete,而不是delete[]

6.使用唯一指针销毁资源
代码语言:javascript
复制
class ClassA {};

class ClassADeleter
{
public:
    void operator () (ClassA* obj) {
        cout << "call ClassA object's Deleter" << endl;
        delete obj;
    }
};

int main()
{
    unique_ptr<ClassA, ClassADeleter> up(new ClassA());
    return 0;
}

唯一指针引用的对象在销毁时需要进行除delete或delete []之外的其它操作时,必须自定义删除器 定义删除器的方法是必须将删除器的类型指定为第二个模板参数 删除器类型可以是函数、函数指针或函数对象

代码语言:javascript
复制
unique_ptr<int, void(*)(int*)> 
uq(new int[666],
	[](int* pointer) {
	...
	delete[] pointer;
	});

unique_ptr<int, function<void(int*)>> 
uq(new int[666],
	[](int* pointer) {
	...
	delete[] pointer;
	});

auto T = [](int* pointer) {
    ...
    delete[] p;
};
unique_ptr<int, decltype(l)>> uq(new int[666], T);

销毁其它类型资源时,需要指定函数或lambda表达式,必须将删除程序的类型声明为void(*)(T *)或 function <void (T *)>或使用decltype

三、自动指针

C++98中存在,于C++11中使用唯一指针替换其它

四、智能指针接口汇总

API Name

Func Def

weak_ptr wp

默认构造函数;创建一个空的弱指针

weak_ptr wp(wp2)

创建一个弱指针,共享由wp2拥有的指针的所有权

wp.~weak_ptr()

析构函数;销毁弱指针,但对拥有的对象无效

wp = wp2

赋值(wp之后共享wp2的所有权,放弃先前拥有的对象的所有权)

wp = sp

用共享指针sp进行赋值(wp之后共享sp的所有权,放弃先前拥有的对象的所有权)

wp.swap(wp2)

交换wp和wp2的指针

swap(wp1,wp2)

交换wp1和wp2的指针

wp.reset()

放弃拥有对象的所有权(如果有的话),并重新初始化为空的弱指针

wp.use_count()

返回共享所有者的数量(拥有对象的shared_ptr数目);如果弱指针为空,则返回0

wp.expired()

返回wp是否为空(等同于wp.use_count() == 0,但可能更快)

wp.lock()

返回共享指针,该共享指针共享弱指针拥有的指针所有权(如果没有共享指针,则为空共享指针)

wp.owner_before(wp2)

提供严格的弱排序和另一个弱指针

wp.owner_before(sp)

通过共享指针提供严格的弱排序

shared_ptr sp(ptr,del)

使用del作为删除器创建拥有*ptr的共享指针

shared_ptr sp(ptr, del, ac)

使用del作为删除器并使用ac作为分配器创建一个拥有*ptr的共享指针

shared_ptr sp(nullptr)

使用默认删除器(调用delete)创建空的共享指针

shared_ptr sp(nullptr, del)

使用del作为删除器创建一个空的共享指针

shared_ptr sp(nullptr, del, ac)

使用del作为删除器和ac作为分配器创建一个空的共享指针

shared_ptr sp(sp2)

创建与sp2共享所有权的共享指针

shared_ptr sp(move(sp2))

创建一个共享指针,该共享指针拥有先前由sp2拥有的指针(sp2之后为空)

shared_ptr sp(sp2, ptr)

别名构造函数;创建一个共享指针,共享sp2的所有权,但引用*ptr

shared_ptr sp(wp)

从弱指针wp创建共享指针

shared_ptr sp(move(up))

从unique_ptr创建共享指针

shared_ptr sp(move(ap))

从auto_ptr创建共享指针

sp.~shared_ptr()

析构函数;如果sp拥有对象,则调用deleter

sp = sp2

赋值(sp之后与sp2共享所有权,放弃先前拥有的对象的所有权)

sp = move(sp2)

移动赋值(sp2将所有权转移到sp)

sp = move(up)

使用unique_ptr进行移动赋值(up将所有权转让给sp)

sp = move(ap)

使用auto_ptr进行移动赋值(ap将所有权转让给sp)

sp1.swap(sp2)

交换sp1和sp2的指针和删除器

swap(sp1, sp2)

交换sp1和sp2的指针和删除器

sp.reset()

放弃所有权并将共享指针重新初始化为空

sp.reset(ptr)

放弃所有权并使用默认删除器(称为delete)重新初始化共享指针,拥有*ptr

sp.reset(ptr, del)

放弃所有权并使用del作为删除器重新初始化共享指针,拥有* ptr

sp.reset(ptr, del, ac)

放弃所有权并重新初始化共享指针,拥有* ptr,使用del作为删除器,使用ac作为分配器

make_shared(…)

为通过传递的参数初始化的新对象创建共享指针

allocate_shared(ac, …)

使用分配器ac为由传递的参数初始化的新对象创建共享指针

sp.get()

返回存储的指针(通常是拥有对象的地址,如果没有则返回nullptr)

*sp

返回拥有的对象(如果没有则为未定义的行为)

sp->…

提供对拥有对象的成员访问权限(如果没有,则行为未定义)

sp.use_count()

返回共享所有者(包括sp)的数目;如果共享指针为空,则返回0

sp.unique()

返回sp是否是唯一所有者(等效于sp.use_count()== 1,但可能更快)

static_pointer_cast(sp)

sp的static_cast<>语义

dynamic_pointer_cast(sp)

sp的dynamic_cast<>语义

const_pointer_cast(sp)

sp的const_cast<>语义

get_deleter(sp)

返回删除器的地址(如果有),否则返回nullptr

strm << sp

调用原始指针的输出运算符(等于strm << sp.get())

sp.owner_before(sp2)

提供严格的弱排序和另一个共享指针

sp.owner_before(wp)

通过弱指针提供严格的弱排序

unique_ptr<…> up

默认构造函数;使用默认/传递的删除器类型的实例作为删除器,创建一个空的唯一指针

unique_ptr up(nullptr)

使用默认/传递的删除器类型的实例作为删除器,创建一个空的唯一指针

unique_ptr<…> up(ptr)

使用默认/传递的删除器类型的实例作为删除器,创建拥有* ptr的唯一指针

unique_ptr<…> up(ptr,del)

使用del作为删除器创建拥有* ptr的唯一指针

unique_ptr up(move(up2))

创建一个拥有up2先前拥有的指针的唯一指针(此后up2为空)

unique_ptr up(move(ap))

创建一个拥有先前由auto_ptr ap拥有的指针的唯一指针(此后ap为空)

up.~unique_ptr()

析构函数;调用拥有者对象的删除器

up = move(up2)

移动赋值(up2将所有权转移到up)

up = nullptr

调用拥有者对象的删除器,并使其为空(等同于up.reset())

up1.swap(up2)

交换up1和up2的指针和删除器

swap(up1,up2)

交换up1和up2的指针和删除器

up.reset()

调用拥有者对象的删除器,并使其为空(相当于up = nullptr)

up.reset(ptr)

调用拥有者对象的删除器,并将共享指针重新初始化为自己的* ptr

up.release()

将所有权放弃给调用者(不调用删除器就返回拥有的对象)

up.get()

返回存储的指针(拥有的对象的地址;如果没有,则返回nullptr)

*up

仅单个对象;返回拥有的对象(如果没有,则为未定义的行为)

up->…

仅单个对象;提供拥有对象的成员访问权限(如果没有,则为未定义的行为)

up[idx]

仅数组对象;返回具有存储数组的索引idx的元素(如果没有,则为未定义的行为)

up.get_deleter()

返回删除器的引用

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:智能指针在C++11中引入,分为三类:
  • 一、共享指针
    • 1. 创建方式:
      • 2. 使用:
        • 3. 销毁:
          • 附:弱指针(weak_ptr)
          • 二、唯一指针
            • 1. 手动释放资源存在的问题:
              • 2. 使用
                • 3. 检查唯一指针是否拥有对象的三种方法:
                  • 4. 指针对象唯一性的转移
                    • 5.使用唯一指针处理数组
                      • 6.使用唯一指针销毁资源
                      • 三、自动指针
                      • 四、智能指针接口汇总
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档