0

0

C++shared_ptr与函数参数传递使用方法

P粉602998670

P粉602998670

发布时间:2025-09-10 11:34:01

|

235人浏览过

|

来源于php中文网

原创

传值用于共享所有权,确保对象生命周期;传const引用仅访问对象,效率更高;裸指针适用于零开销场景但风险高;多线程中应传值并同步对象访问。

c++shared_ptr与函数参数传递使用方法

C++中

shared_ptr
作为函数参数传递,核心在于明确你希望函数如何参与到对象的生命周期管理中。简单来说,如果你希望函数成为对象的“共同所有者”之一,或者需要延长对象的生命周期,那就传值;如果函数只是想“观察”或使用对象,而不影响其生命周期,那么传
const
引用是更高效且清晰的选择。至于裸指针或裸引用,那是在你对对象生命周期有绝对把握,且函数完全不涉及所有权管理时的选择,但风险也随之而来。

解决方案

在使用

shared_ptr
作为函数参数时,有几种主要策略,每种都有其适用场景和考量:

  1. 传值 (Pass by Value):

    void func(std::shared_ptr obj)

    • 何时使用: 当函数需要成为对象的一个新的共同所有者时。这意味着函数内部会持有对象的一个副本,并确保对象在函数执行期间,甚至在函数返回后,只要这个副本还存在,对象就不会被销毁。例如,将对象存储在一个容器中,或者将其传递给一个异步任务。
    • 优点: 语义清晰,明确表示函数将共享所有权。在多线程环境中,将
      shared_ptr
      按值传递给新线程是确保对象生命周期的安全方式。
    • 缺点: 会增加引用计数,并可能产生一次
      shared_ptr
      对象的拷贝开销(尽管通常只是指针和控制块的拷贝,开销不大)。
  2. const
    引用 (Pass by
    const
    Reference):
    void func(const std::shared_ptr& obj)

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

    • 何时使用: 这是最常见且推荐的方式,当函数只需要访问或使用
      shared_ptr
      所管理的对象,但不需要共享所有权,也不需要延长对象的生命周期时。函数只是一个“观察者”。
    • 优点: 效率最高,不会增加引用计数,避免了
      shared_ptr
      对象的拷贝。语义明确,表示函数不会修改
      shared_ptr
      本身,也不会成为新的所有者。
    • 缺点: 函数本身不会阻止对象被销毁。如果函数内部需要存储这个
      shared_ptr
      ,它必须显式地进行拷贝。
  3. 传非

    const
    引用 (Pass by Non-
    const
    Reference):
    void func(std::shared_ptr& obj)

    • 何时使用: 比较少见,但当函数需要修改
      shared_ptr
      本身时(例如,将其
      reset()
      ,或者替换为另一个
      shared_ptr
      )才使用。
    • 优点: 允许函数直接操作传入的
      shared_ptr
      实例。
    • 缺点: 容易引起混淆,因为所有权语义变得不那么直观。需要非常明确的理由才使用。
  4. *传裸指针或裸引用 (Pass by Raw Pointer or Raw Reference): `void func(MyClass obj_ptr)

    void func(MyClass& obj_ref)`**

    • 何时使用: 当函数完全不关心对象的生命周期,仅仅需要访问对象的数据或调用其方法时。这通常用于“sink”函数,它们只是处理数据,并且其执行时间严格在
      shared_ptr
      所管理对象的生命周期内。
    • 优点: 零开销,最接近C语言的传统指针/引用传递。
    • 缺点: 丧失了
      shared_ptr
      提供的所有权管理和自动内存释放的安全性。如果
      shared_ptr
      在函数执行期间提前释放了对象,将导致悬空指针/引用,引发未定义行为。这需要调用者和函数开发者之间有非常强的契约保证。

shared_ptr
作为函数参数时,选择传值还是传引用,有什么讲究?

这确实是个值得深思的问题,很多时候,初学者会觉得“传引用更高效”,然后不加区分地使用。但实际上,这两种方式承载着完全不同的语义。

当你将

shared_ptr
按值传递时,比如
void process(std::shared_ptr data)
,你是在明确地告诉调用者和阅读代码的人:这个
process
函数会获得
data
对象的一个共享所有权。这意味着
data
对象的引用计数会增加1。函数内部可以安全地存储这个
shared_ptr
的副本,甚至在函数返回后,只要这个副本还存在,
data
对象就不会被销毁。这在很多场景下非常有用,比如你有一个任务队列,需要将一个
shared_ptr
对象提交给后台线程处理。如果按值传递,后台线程就拥有了它自己的
shared_ptr
副本,确保了数据在处理期间的有效性,而不用担心原始的
shared_ptr
提前失效。它是一种“我需要这份数据,并且我要确保它在我用完之前不会消失”的表达。

而当你选择

const
引用传递时,例如
void inspect(const std::shared_ptr& data)
,你的意图是完全不同的。你是在说:
inspect
函数只是想“看一眼”
data
对象,使用它,但它不打算成为
data
的任何所有者,也不打算影响
data
的生命周期。引用计数不会增加。这通常是最高效的方式,因为它避免了引用计数的原子操作开销。这种方式适用于那些只读操作、打印日志、或者仅仅是临时访问对象内容的函数。它传递的是一种“我需要访问这份数据,但我相信它会活得比我长,或者说,我不需要为它的生命负责”的信号。如果函数内部需要存储这份数据,它必须显式地调用
std::shared_ptr my_copy = data;
来创建自己的共享所有权。

所以,关键在于你函数的设计意图:是想共享所有权,还是仅仅想临时访问?这两种选择的背后,是对资源生命周期管理的不同策略。没有绝对的优劣,只有是否符合当前场景的设计需求。我个人经验是,如果拿不准,先考虑

const std::shared_ptr&
,它通常是安全的默认选择。但如果涉及到异步、存储或任何可能延长对象生命周期的操作,那就果断传值。

什么时候应该考虑将
shared_ptr
管理的对象以裸指针或裸引用形式传递?

这其实是一个关于信任和责任的边界问题。将

shared_ptr
管理的对象以裸指针或裸引用形式传递(例如
void do_something(MyObject* obj)
void do_something_else(MyObject& obj)
),意味着你暂时放弃了
shared_ptr
提供的智能管理,将对象的生命周期责任完全交还给了调用者。

那么,什么时候会这么做呢?

站长俱乐部购物系统
站长俱乐部购物系统

功能介绍:1、模块化的程序设计,使得前台页面设计与程序设计几乎完全分离。在前台页面采用过程调用方法。在修改页面设计时只需要在相应位置调用设计好的过程就可以了。另外,这些过程还提供了不同的调用参数,以实现不同的效果;2、阅读等级功能,可以加密产品,进行收费管理;3、可以完全可视化编辑文章内容,所见即所得;4、无组件上传文件,服务器无需安装任何上传组件,无需支持FSO,即可上传文件。可限制文件上传的类

下载

一个常见的场景是,当你的函数是一个纯粹的“操作”函数,它只关心对对象进行某个操作,而完全不关心这个对象的创建、销毁或所有权。比如,一个

draw(const Shape& s)
函数,它只负责把一个形状画出来。这个
Shape
对象可能是由
shared_ptr
管理的,也可能是栈上的,或者其他智能指针管理的。
draw
函数根本不应该关心这些。它只需要一个有效的
Shape
实例来执行它的绘图逻辑。

另一个情况是,当你知道你的函数执行周期非常短,并且严格嵌套在

shared_ptr
的生命周期内。换句话说,你百分之百确定,在
do_something(obj_ptr)
函数执行期间,那个
obj_ptr
所指向的对象绝对不会被销毁。在这种情况下,使用裸指针或裸引用可以避免
shared_ptr
的引用计数开销,尤其是在性能敏感的循环中。

然而,这种做法伴随着巨大的风险。一旦你的假设——即对象在函数执行期间不会被销毁——被打破,你就会遇到悬空指针/引用,导致程序崩溃或未定义行为。这种错误往往难以调试,因为它取决于复杂的生命周期交互。

所以,我的建议是:

  • 优先使用
    const std::shared_ptr&
    ,除非有明确的理由。
  • 仅在以下情况考虑裸指针/引用:
    • 函数是一个通用的算法,不应该被绑定到特定的所有权管理机制(比如
      std::sort
      接受迭代器)。
    • 性能是极端关键的考量,并且你能够通过设计保证裸指针/引用的安全性(例如,函数是某个类的私有方法,且仅在
      shared_ptr
      保证存活的公有方法内部调用)。
    • 函数签名需要兼容C风格API。
  • 绝不将裸指针或裸引用存储起来,或者将其传递给异步操作,因为这几乎肯定会导致生命周期问题。它们应该只用于即时访问。

本质上,使用裸指针/引用是一种性能优化或通用性需求,但它要求开发者承担更多的生命周期管理责任。

shared_ptr
在多线程环境下作为参数传递时,有哪些陷阱和最佳实践?

多线程环境下的

shared_ptr
参数传递是一个需要格外小心的领域,因为这里面不仅涉及到对象本身的生命周期,还涉及线程同步和数据竞争。

一个常见的陷阱是对裸指针或裸引用的不当使用。想象一下,你有一个

shared_ptr data_ptr
,然后你启动了一个新线程,并将
data_ptr.get()
(即裸指针)传递给它。如果主线程在子线程完成工作之前,
data_ptr
的引用计数降到零,导致
TaskData
对象被销毁,那么子线程就会操作一个悬空指针,这几乎是灾难性的。
shared_ptr
本身对引用计数的增减是线程安全的(原子操作),但这并不意味着它管理的对象也是线程安全的,更不意味着裸指针是安全的。

另一个陷阱是,即使你正确地传递了

shared_ptr
,如果多个线程都持有同一个
shared_ptr
的副本,并且同时修改它所管理的对象,那么就会发生数据竞争。
shared_ptr
只保证它自身的控制块是线程安全的,不保证
T
类型的对象是线程安全的。例如,如果
TaskData
内部有一个
int counter
,多个线程同时调用
data_ptr->increment_counter()
,而
increment_counter
没有加锁,那
counter
的值就可能出错。

那么,最佳实践是什么呢?

  1. 向新线程或异步任务传递

    shared_ptr
    时,务必按值传递: 这是最安全、最推荐的做法。当你启动一个新线程或提交一个异步任务时,例如:

    std::shared_ptr data = std::make_shared();
    // ... 对data进行初始化 ...
    
    std::thread t([data_copy = data]() { // data_copy 按值捕获
        // 在新线程中使用 data_copy
        data_copy->process();
    });
    t.detach(); // 或 t.join();

    通过按值捕获(C++11的lambda捕获列表)或者按值传递给函数参数,新线程会获得

    shared_ptr
    的一个独立副本。这会增加引用计数,并确保
    MyData
    对象在子线程完成其工作之前不会被销毁。这是确保对象生命周期在跨线程边界安全延伸的关键。

  2. 保护

    shared_ptr
    所管理对象的内部状态: 如果多个线程需要访问同一个
    shared_ptr
    所管理的对象,并且其中至少有一个线程会修改对象的状态,那么你必须使用互斥锁(
    std::mutex
    )、原子操作(
    std::atomic
    )或其他同步原语来保护对象的内部数据。

    class ThreadSafeData {
        mutable std::mutex mtx_; // mutable 允许在const方法中加锁
        int value_;
    public:
        void increment() {
            std::lock_guard lock(mtx_);
            value_++;
        }
        int get_value() const {
            std::lock_guard lock(mtx_);
            return value_;
        }
    };
    
    std::shared_ptr shared_data = std::make_shared();
    // 多个线程可以安全地调用 shared_data->increment() 或 shared_data->get_value()

    记住,

    shared_ptr
    只保证其自身的线程安全,不保证它所指向的对象是线程安全的。

  3. 谨慎使用

    std::weak_ptr
    来观察对象,避免循环引用: 在多线程或复杂对象图中,
    weak_ptr
    是一个重要的工具。它允许你观察一个由
    shared_ptr
    管理的对象,而不会增加其引用计数,从而避免循环引用导致的内存泄漏。当你需要访问对象时,可以尝试从
    weak_ptr
    获取一个
    shared_ptr
    weak_ptr::lock()
    ),如果对象仍然存活,你就会得到一个有效的
    shared_ptr
    。这在缓存管理、事件监听器等场景中非常有用。虽然它不是直接的参数传递方式,但在多线程中管理对象生命周期时,它与
    shared_ptr
    是密切相关的。

总之,多线程环境下的

shared_ptr
使用,核心在于“所有权”和“数据竞争”这两个维度。按值传递
shared_ptr
来安全地共享所有权,并始终为共享可变状态的对象添加适当的同步机制,这是避免陷阱的关键。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

397

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

618

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

354

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

258

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

600

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

526

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

641

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

601

2023.09.22

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 4.6万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

Go 教程
Go 教程

共32课时 | 4万人学习

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

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