0

0

C++如何在函数中传递动态分配对象

P粉602998670

P粉602998670

发布时间:2025-09-15 10:06:01

|

644人浏览过

|

来源于php中文网

原创

优先使用智能指针传递动态分配对象,std::unique_ptr通过std::move转移独占所有权,确保资源安全释放;std::shared_ptr通过引用计数实现共享所有权,适合多部分共享对象的场景;避免原始指针以防止内存泄漏和悬空指针。

c++如何在函数中传递动态分配对象

在C++函数中传递动态分配的对象,核心考量在于如何清晰地管理对象的所有权和生命周期。简单来说,最佳实践是优先使用智能指针,特别是

std::unique_ptr
std::shared_ptr
,它们能有效避免传统原始指针带来的内存泄漏、悬空指针和双重释放等问题,让代码更健壮、更易维护。

解决方案

要安全、高效地在C++函数中传递动态分配的对象,我们主要有以下几种策略,每种都对应着不同的所有权语义:

  1. 传递独占所有权(

    std::unique_ptr
    当你希望一个函数获得一个动态分配对象的唯一所有权,并且在函数结束后(或将所有权转移到其他地方后)负责其生命周期管理时,
    std::unique_ptr
    是理想选择。你通常会通过值传递
    std::unique_ptr
    ,并使用
    std::move
    来显式转移所有权。这意味着原始的
    unique_ptr
    在调用后将变为空,不再拥有该对象。

    #include 
    #include 
    
    class MyResource {
    public:
        MyResource(int id) : id_(id) { std::cout << "MyResource " << id_ << " created.\n"; }
        ~MyResource() { std::cout << "MyResource " << id_ << " destroyed.\n"; }
        void use() const { std::cout << "Using MyResource " << id_ << ".\n"; }
    private:
        int id_;
    };
    
    // 函数接收独占所有权
    void processUniqueResource(std::unique_ptr res) {
        if (res) {
            res->use();
        }
        // res 在这里超出作用域时会自动销毁其指向的对象
    }
    
    // 示例用法
    // int main() {
    //     std::unique_ptr r1 = std::make_unique(1);
    //     processUniqueResource(std::move(r1)); // 所有权转移
    //     // r1 现在是空的,不能再访问
    //     // if (r1) { /* 这段代码不会执行 */ }
    //     return 0;
    // }
  2. 传递共享所有权(

    std::shared_ptr
    当一个动态分配的对象需要被多个部分共享,并且其生命周期应该由所有共享者共同决定时,
    std::shared_ptr
    就派上用场了。它通过引用计数机制来管理对象的生命周期,只有当所有
    shared_ptr
    实例都销毁后,对象才会被释放。你可以通过值传递
    std::shared_ptr
    来增加引用计数,或者通过常量引用传递
    const std::shared_ptr&
    来观察智能指针本身而不影响引用计数。

    #include 
    #include 
    
    class SharedResource {
    public:
        SharedResource(int id) : id_(id) { std::cout << "SharedResource " << id_ << " created.\n"; }
        ~SharedResource() { std::cout << "SharedResource " << id_ << " destroyed.\n"; }
        void report() const { std::cout << "Reporting from SharedResource " << id_ << ".\n"; }
    private:
        int id_;
    };
    
    // 函数接收共享所有权
    void processSharedResource(std::shared_ptr res) {
        if (res) {
            res->report();
            std::cout << "  Inside processSharedResource, use_count: " << res.use_count() << "\n";
        }
        // res 离开作用域时,引用计数减一
    }
    
    // 函数仅观察 shared_ptr 本身,不影响所有权
    void inspectSharedPtr(const std::shared_ptr& resPtr) {
        if (resPtr) {
            std::cout << "  Inspecting shared_ptr, use_count: " << resPtr.use_count() << "\n";
        }
    }
    
    // 示例用法
    // int main() {
    //     std::shared_ptr s1 = std::make_shared(10);
    //     std::cout << "Initial use_count: " << s1.use_count() << "\n"; // 1
    
    //     processSharedResource(s1); // 传递值,引用计数增加
    //     std::cout << "After processSharedResource, use_count: " << s1.use_count() << "\n"; // 1
    
    //     inspectSharedPtr(s1); // 传递常量引用,引用计数不变
    //     std::cout << "After inspectSharedPtr, use_count: " << s1.use_count() << "\n"; // 1
    
    //     {
    //         std::shared_ptr s2 = s1; // 复制,引用计数增加
    //         std::cout << "Inside block, use_count: " << s1.use_count() << "\n"; // 2
    //     } // s2 销毁,引用计数减一
    //     std::cout << "After block, use_count: " << s1.use_count() << "\n"; // 1
    //     return 0;
    // }
  3. 传递非所有权(原始指针或引用) 有时候,一个函数仅仅需要访问动态分配的对象,而不需要参与其所有权管理。在这种情况下,你可以从智能指针中获取原始指针(

    get()
    方法)或引用(
    *
    解引用),然后将它们传递给函数。但这要求调用者保证在函数执行期间,对象仍然存活。这种方式适用于观察者模式或那些不关心对象生命周期的辅助函数。

    // 函数仅使用对象,不关心所有权
    void useResourceDirectly(MyResource* res) {
        if (res) {
            res->use();
        }
    }
    
    void useResourceByRef(MyResource& res) {
        res.use();
    }
    
    // 示例用法
    // int main() {
    //     std::unique_ptr r2 = std::make_unique(2);
    //     useResourceDirectly(r2.get()); // 传递原始指针
    //     useResourceByRef(*r2);       // 传递引用
    //     return 0;
    // }

为什么直接传递原始指针(Raw Pointer)是个坏主意?

说实话,在现代C++中,直接通过原始指针(

T*
)来传递动态分配的对象,尤其是当函数可能需要管理其生命周期时,简直是自找麻烦。我个人觉得,这玩意儿就是一堆潜在问题的温床,特别容易导致:

  • 所有权不明确:这是最大的痛点。当一个函数接收
    T*
    时,它到底应该负责
    delete
    这个对象,还是仅仅使用它?如果它
    delete
    了,那么调用者还能不能访问?如果调用者也
    delete
    ,那不就双重释放了吗?这种模糊性是导致内存泄漏和程序崩溃的根本原因。
  • 悬空指针(Dangling Pointers):如果一个对象被提前释放了,而其他地方的原始指针还在引用它,那么这些指针就成了悬空指针。一旦通过它们访问内存,轻则程序崩溃,重则数据损坏,而且这种错误往往难以追踪。
  • 双重释放(Double Free):如果多个原始指针指向同一个动态分配的对象,并且它们都尝试去
    delete
    它,那就会发生双重释放。这通常会导致未定义行为,程序直接就崩给你看。
  • 异常安全问题:在复杂的代码流中,如果函数内部发生异常,原始指针可能无法在正确的时间被
    delete
    ,从而导致内存泄漏。智能指针在这方面表现得好得多,它们利用RAII(资源获取即初始化)原则,确保在任何情况下都能正确释放资源。
  • 缺乏语义表达:原始指针仅仅是一个内存地址,它无法表达任何关于对象生命周期的意图。而智能指针,比如
    unique_ptr
    shared_ptr
    ,其类型本身就清晰地传达了所有权语义,让代码意图一目了然。

所以,除非你明确知道对象的所有权由别处严格管理,并且你的函数只是一个临时的“观察者”,否则,尽量远离直接传递原始指针来管理动态对象。这不仅仅是编码规范的问题,更是为了代码的健壮性和可维护性。

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

std::unique_ptr
如何实现独占所有权传递?

std::unique_ptr
实现独占所有权传递的核心在于它的移动语义(Move Semantics)。顾名思义,它强调的是“移动”而非“复制”。一个
unique_ptr
实例是它所管理对象的唯一所有者,你不能简单地复制它,因为那样就会有两个指针指向同一个对象,这与“独占”的理念相悖。

当你把一个

std::unique_ptr
作为函数参数通过值传递时,你需要显式地使用
std::move
。这个
std::move
操作并不会复制对象,而是将原
unique_ptr
的所有权“转移”给函数参数。一旦所有权被转移,原来的
unique_ptr
就变成了一个空指针(不再指向任何对象),而函数参数现在则拥有了该对象。当函数执行完毕,这个函数参数
unique_ptr
超出作用域时,它会自动调用其析构函数,从而安全地删除所管理的对象。

晓象AI资讯阅读神器
晓象AI资讯阅读神器

晓象-AI时代的资讯阅读神器

下载

这种机制完美地解决了原始指针的所有权模糊问题:谁接收了

unique_ptr
,谁就负责它的生命周期。这就像你把一件独一无二的宝物交给了另一个人,宝物现在是他的了,你手上就没有了。

#include 
#include 

class Gadget {
public:
    Gadget(int id) : id_(id) { std::cout << "Gadget " << id_ << " created.\n"; }
    ~Gadget() { std::cout << "Gadget " << id_ << " destroyed.\n"; }
    void operate() const { std::cout << "Operating Gadget " << id_ << ".\n"; }
private:
    int id_;
};

// 接收独占所有权,处理后销毁
void processAndDispose(std::unique_ptr g) {
    if (g) {
        g->operate();
        std::cout << "  Gadget " << g->id_ << " processed.\n";
    }
    // g 在这里离开作用域,自动调用 ~Gadget()
}

// 仅仅观察 Gadget,不获取所有权
void inspectGadget(const Gadget& g) {
    g.operate();
    std::cout << "  Gadget " << g.id_ << " inspected by reference.\n";
}

int main() {
    std::unique_ptr myGadget = std::make_unique(101);
    std::cout << "Main scope: myGadget created.\n";

    // 传递原始指针或引用给不获取所有权的函数
    inspectGadget(*myGadget);

    // 转移所有权给 processAndDispose
    processAndDispose(std::move(myGadget));
    std::cout << "Main scope: After processAndDispose call.\n";

    // 此时 myGadget 已经为空,访问会是未定义行为
    if (!myGadget) {
        std::cout << "Main scope: myGadget is now empty.\n";
    }

    // 如果想在函数内部修改 unique_ptr 本身(比如让它指向新的对象),
    // 可以传递 unique_ptr 的引用,但这种情况不常见,且需要小心所有权管理
    // void modifyUniquePtr(std::unique_ptr& ptr) {
    //     ptr = std::make_unique(202);
    // }
    // modifyUniquePtr(myGadget); // 此时 myGadget 又指向新对象了

    return 0;
}

通过这个例子,我们能清楚看到

std::move
如何将
myGadget
的所有权转移给
processAndDispose
函数内部的
g
,而
myGadget
本身则失去了对对象的控制。这种模式在工厂函数、资源管理对象需要被传递给消费者等场景下非常有用。

std::shared_ptr
在共享所有权场景下的应用

std::shared_ptr
是C++中处理共享所有权的利器。它内部通过一个引用计数器来追踪有多少个
shared_ptr
实例正在共享同一个动态分配的对象。每当一个新的
shared_ptr
实例被创建并指向同一个对象时,引用计数就加一;每当一个
shared_ptr
实例被销毁或重新指向其他对象时,引用计数就减一。只有当引用计数归零时,
shared_ptr
才会自动删除它所管理的对象。

这种机制特别适合那些对象生命周期不确定,或者需要被多个不相关的模块共同持有和访问的场景,比如:

  • 缓存系统:缓存中的对象可能被多个客户端访问,只有当所有客户端都不再需要时才将其从内存中移除。
  • 回调函数:当一个异步操作完成后,需要访问一个特定的对象,但你不知道这个对象在回调触发时是否还存活。
    shared_ptr
    可以确保对象在回调期间一直有效。
  • 图结构或循环引用:虽然
    shared_ptr
    本身可能导致循环引用(需要
    std::weak_ptr
    来打破),但在没有循环引用的情况下,它能很好地管理共享对象。

在函数中传递`std::shared

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

1498

2023.10.24

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

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

529

2023.09.20

c++怎么把double转成int
c++怎么把double转成int

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

73

2025.08.29

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

101

2025.10.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

395

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

java值传递和引用传递有什么区别
java值传递和引用传递有什么区别

java值传递和引用传递的区别:1、基本数据类型的传递;2、对象的传递;3、修改引用指向的情况。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

108

2024.02.23

java值传递和引用传递有什么区别
java值传递和引用传递有什么区别

java值传递和引用传递的区别:1、基本数据类型的传递;2、对象的传递;3、修改引用指向的情况。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

108

2024.02.23

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

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

精品课程

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

共1课时 | 0.1万人学习

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

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

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

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