0

0

C++智能指针比较运算 所有权比较规则

P粉602998670

P粉602998670

发布时间:2025-09-01 10:29:01

|

1031人浏览过

|

来源于php中文网

原创

智能指针的比较不仅限于地址,std::unique_ptr直接比较指针地址,而std::shared_ptr和std::weak_ptr通过std::owner_less比较是否共享同一控制块,以判断所有权身份,尤其在容器键值、缓存和观察者模式中至关重要。

c++智能指针比较运算 所有权比较规则

在C++智能指针的世界里,比较运算远不止于简单的地址判断。当我们谈及智能指针的“所有权比较规则”时,核心在于理解它们在面对底层资源时,如何界定“相同”或“不同”的管理关系。对于

std::unique_ptr
,比较通常只涉及其内部裸指针的地址。但对于
std::shared_ptr
std::weak_ptr
,所有权比较则是一个更深层次的概念,它关乎两个指针是否共享同一个控制块(control block),从而共同管理着同一份资源。

C++智能指针的比较运算,尤其是涉及所有权的概念,其实是围绕着不同智能指针类型及其设计哲学展开的。

std::unique_ptr
的比较相对直接。由于它独占资源,任何两个非空的
std::unique_ptr
如果指向同一地址,那通常意味着其中一个已经通过
std::move
转移了所有权,或者存在逻辑上的错误。因此,
std::unique_ptr
==
!=
<
等比较运算符,本质上都是对其内部存储的裸指针进行比较。它们之间没有“共享所有权”的概念,所以也就谈不上所有权层面的比较规则。比如,
p1 == p2
实际上就是
p1.get() == p2.get()

std::shared_ptr
std::weak_ptr
的情况则复杂得多,也更有趣。它们引入了“控制块”的概念,这是实现共享所有权和生命周期管理的关键。

立即学习C++免费学习笔记(深入)”;

  • std::shared_ptr
    的默认比较 (
    ==
    ,
    !=
    )
    std::shared_ptr
    重载的
    ==
    !=
    运算符,和
    std::unique_ptr
    类似,也是比较它们所持有的裸指针地址。也就是说,
    sp1 == sp2
    等价于
    sp1.get() == sp2.get()
    。这仅仅判断两个智能指针是否指向了内存中的同一个具体对象。这并不能完全反映它们在所有权上的关系。例如,你可能有两个
    shared_ptr
    ,一个指向基类子对象,一个指向派生类对象,它们可能指向同一块内存的起始地址,但它们的类型不同。更重要的是,它们可能通过别名构造函数(aliasing constructor)指向不同的地址,但却共享同一个控制块。

  • 所有权比较 (

    std::owner_less
    ): 为了真正比较智能指针的“所有权身份”,C++标准库提供了
    std::owner_less
    模板。这是一个函数对象,用于为
    std::shared_ptr
    std::weak_ptr
    提供严格弱序(strict weak ordering),其核心就是比较它们是否共享同一个控制块。如果两个智能指针共享同一个控制块,那么它们就拥有相同的“所有权身份”,即便它们通过
    get()
    返回的裸指针地址可能不同(例如,通过别名构造函数创建的
    shared_ptr
    )。

    std::owner_less
    对于在有序容器(如
    std::map
    std::set
    )中使用智能指针作为键至关重要。它确保了即便智能指针所指向的裸指针地址不同,只要它们管理的是同一份资源(即共享同一控制块),就会被视为等价。

    std::weak_ptr
    的比较也遵循类似的逻辑。
    std::weak_ptr
    ==
    !=
    运算符会比较其内部裸指针的地址,并且如果两个
    weak_ptr
    都已过期(expired),它们也被认为是相等的。而
    std::owner_less
    则允许你比较
    std::weak_ptr
    甚至
    std::shared_ptr
    std::weak_ptr
    之间是否共享同一控制块,这对于判断它们是否“观察”着同一份资源非常有用,即使资源可能已经失效。

为什么
std::shared_ptr
的相等比较 (
==
) 和所有权比较 (
std::owner_less
) 行为不同?

这其实是一个非常关键的细节,也是很多初学者容易混淆的地方。在我看来,这种差异的设计是出于实用性和灵活性两方面的考虑。

std::shared_ptr
operator==
遵循的是大多数指针类型比较的直观语义:它比较的是两个指针所“指向”的内存地址是否相同。这就像你问“这两本书是不是同一本?”你可能只是看它们的封面标题是否一样。在很多场景下,我们确实只需要知道两个
shared_ptr
是否指向了内存中的同一个对象实例。比如,你可能在遍历一个列表,想看看某个
shared_ptr
是否已经存在于列表中,此时比较
get()
返回的地址是最直接、效率也最高的做法。

然而,

std::owner_less
提供的所有权比较,则更像是问“这两本书是不是由同一个出版商、同一个作者创作的,即便它们的版本或装帧不同?”它关注的是更深层次的“身份”——控制块。一个经典的例子就是
shared_ptr
的别名构造函数(aliasing constructor)。

Musico
Musico

Musico 是一个AI驱动的软件引擎,可以生成音乐。 它可以对手势、动作、代码或其他声音做出反应。

下载
struct Resource {
    int id;
    std::string name;
};

struct Member {
    int value;
};

// 假设Resource里有一个Member类型的成员
struct ComplexResource {
    int resource_id;
    Member internal_member;
};

std::shared_ptr res_ptr(new ComplexResource{1, {100}});
// 创建一个shared_ptr,它与res_ptr共享所有权,但指向的是res_ptr管理的对象的某个成员
std::shared_ptr member_ptr(res_ptr, &res_ptr->internal_member);

// 此时:
// res_ptr.get() 和 member_ptr.get() 是不同的地址
// res_ptr == member_ptr 会是 false (因为get()不同)

// 但是,它们共享同一个控制块,因为member_ptr的生命周期依赖于res_ptr
// 所以,std::owner_less()(res_ptr, member_ptr) 会是 false
// 而 !(std::owner_less()(res_ptr, member_ptr) || std::owner_less()(member_ptr, res_ptr)) 会是 true
// 这表示它们在所有权层面上是等价的。

在这个例子中,

res_ptr
member_ptr
指向的内存地址截然不同,所以
==
比较会返回
false
。但它们却共享同一个控制块,意味着它们的生命周期是绑定在一起的,当
res_ptr
的引用计数归零时,
ComplexResource
对象才会被销毁。
member_ptr
仅仅是提供了一个指向
internal_member
的视图,但它本身并不拥有
internal_member
的独立生命周期。

std::owner_less
的存在,正是为了解决这种“指向不同,但管理同一份资源”的场景。它允许你在需要根据资源管理身份来区分对象时,能够有一个可靠的机制。这在将
shared_ptr
weak_ptr
作为键存储在
std::map
std::set
这样的有序关联容器中时尤为重要,因为这些容器需要一个稳定的、能提供严格弱序的比较器来正确地插入和查找元素。如果只用
operator==
来判断,上面的
res_ptr
member_ptr
就会被认为是两个不同的键,即便它们在逻辑上是关联到同一份核心资源的。

std::weak_ptr
如何参与所有权比较,它与
std::shared_ptr
有何异同?

std::weak_ptr
在所有权比较中扮演的角色,我觉得用“观察者”来形容最为贴切。它本身不拥有资源,只是对
std::shared_ptr
所管理资源的一个“弱引用”。这意味着
std::weak_ptr
不会增加资源的引用计数,因此它的存在不会阻止资源被销毁。

在比较行为上,

std::weak_ptr
std::shared_ptr
有着相似之处,也有其独特之处:

  1. 裸指针比较 (

    ==
    ,
    !=
    )
    std::weak_ptr
    同样重载了
    ==
    !=
    运算符,它们也是比较内部存储的裸指针地址。不过这里有个小细节:如果两个
    weak_ptr
    都已经过期(即它们所观察的
    shared_ptr
    已经销毁了资源),那么它们也会被认为是相等的,因为它们都指向了无效的地址(通常是
    nullptr
    )。这与
    shared_ptr
    略有不同,
    shared_ptr
    只有在
    get()
    返回相同地址时才相等。

  2. 所有权比较 (

    std::owner_less
    ): 这才是
    std::weak_ptr
    在所有权比较中最有价值的地方。
    std::owner_less
    不仅可以比较两个
    shared_ptr
    ,两个
    weak_ptr
    ,甚至可以比较一个
    shared_ptr
    和一个
    weak_ptr
    ,来判断它们是否指向同一个控制块。

    想象这样一个场景:你有一个缓存系统,里面存储了资源的

    std::weak_ptr
    。当需要访问某个资源时,你会尝试
    lock()
    这个
    weak_ptr
    获得一个
    shared_ptr
    。但在某些情况下,你可能需要判断当前正在使用的
    shared_ptr
    是否与缓存中的某个
    weak_ptr
    观察的是同一个资源,即使缓存中的
    weak_ptr
    可能已经过期了。

    std::shared_ptr sp1(new int(10));
    std::weak_ptr wp1 = sp1;
    
    std::shared_ptr sp2(new int(20));
    std::weak_ptr wp2 = sp2;
    
    // sp1 和 wp1 共享同一个控制块
    bool same_owner_sp1_wp1 = !(std::owner_less>()(sp1, wp1) || std::owner_less>()(wp1, sp1)); // true
    
    // sp1 和 wp2 不共享同一个控制块
    bool same_owner_sp1_wp2 = !(std::owner_less>()(sp1, wp2) || std::owner_less>()(wp2, sp1)); // false
    
    // 让 sp1 过期,观察资源被销毁
    sp1.reset();
    // 此时 wp1 已经过期,但它仍然知道自己曾经观察的是哪个控制块
    // 它可以和另一个 shared_ptr 进行所有权比较,看它们是否“曾经”指向同一个资源
    std::shared_ptr sp_new(new int(30));
    bool same_owner_wp1_sp_new = !(std::owner_less>()(wp1, sp_new) || std::owner_less>()(sp_new, wp1)); // false

    std::weak_ptr
    能够通过
    std::owner_less
    参与所有权比较,这使得它在构建复杂的对象生命周期管理和缓存机制时非常强大。它允许我们在不影响资源生命周期的情况下,跟踪并识别资源的“身份”。

在实际开发中,何时以及如何正确使用智能指针的所有权比较?

在实际的C++开发中,正确理解并运用智能指针的所有权比较,能够帮助我们编写出更健壮、更高效的代码,尤其是在处理资源管理和对象生命周期时。

何时使用:

  1. 作为关联容器的键 (Key in Associative Containers): 这是

    std::owner_less
    最常见且最重要的应用场景。如果你需要将
    std::shared_ptr
    std::weak_ptr
    对象作为
    std::map
    std::set
    的键,并且你希望这些容器能够根据智能指针所管理的“同一份资源”来唯一识别键,而不是仅仅根据裸指针地址,那么你就必须使用
    std::owner_less
    作为自定义比较器。

    • 示例场景:一个资源管理器,需要根据资源的唯一“身份”来存储和查找其元数据。
    • 错误做法:直接使用
      std::map, MyMetadata>
      ,这会默认使用
      std::shared_ptr
      operator<
      ,而这个运算符在C++17之前是基于裸指针地址的,C++17之后才被定义为基于
      owner_less
      。为了兼容性和明确性,最好还是显式指定。
  2. 实现缓存机制 (Caching Mechanisms): 在缓存系统中,你可能希望存储

    std::weak_ptr
    来避免持有资源导致其无法销毁,同时又需要能够快速判断一个新来的
    shared_ptr
    是否与缓存中某个
    weak_ptr
    指向同一份(或曾经指向同一份)资源。
    std::owner_less
    就能帮你完成这个任务。

  3. 调试与日志 (Debugging and Logging): 当你在调试复杂的对象图或资源依赖关系时,可能需要验证两个不同的

    shared_ptr
    weak_ptr
    是否确实管理着同一个底层对象。
    std::owner_less
    提供了一个明确的语义来检查这种“所有权身份”的等价性。

  4. Observer 模式或事件系统 (Observer Pattern/Event Systems): 在一些Observer模式的实现中,Subject可能持有对Observer的

    weak_ptr
    。当Subject需要通知Observer时,它会尝试
    lock()
    这些
    weak_ptr
    。如果系统中需要根据Observer的“身份”来注册或取消注册,
    std::owner_less
    可以帮助你识别同一Observer的不同
    weak_ptr
    实例。

如何正确使用:

  1. 对于
    std::unique_ptr
    : 坚持使用
    ==
    !=
    运算符来比较它们所管理的裸指针地址。因为
    unique_ptr
    没有共享所有权的概念,其比较语义就是如此。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1435

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

223

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

84

2025.10.17

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

73

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

25

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

36

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

31

2025.11.27

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

3

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

1

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号