0

0

C++的std::unique_ptr作为函数参数或返回值时应该怎么传递

P粉602998670

P粉602998670

发布时间:2025-09-03 09:02:01

|

271人浏览过

|

来源于php中文网

原创

传递std::unique_ptr时,若仅观察则用const引用,若转移所有权则值传递并std::move,返回时也推荐值返回以实现高效所有权移交。

c++的std::unique_ptr作为函数参数或返回值时应该怎么传递

在C++中,将

std::unique_ptr
作为函数参数或返回值传递,核心原则在于明确所有权(ownership)的语义。简单来说,如果你只是想“看一眼”指针指向的对象,而不改变所有权,就用
const std::unique_ptr&
;如果你想把所有权“交出去”,让函数接管,就用值传递(
std::unique_ptr
)并在调用时使用
std::move
;而从函数返回时,通常也是通过值传递
std::unique_ptr
,让调用者获得所有权。

解决方案

处理

std::unique_ptr
的传递问题,关键在于理解其独占所有权的特性。我们通常会遇到几种场景,每种都有其推荐的做法:

  1. 观察(Observe)对象,不转移所有权: 当函数只需要读取

    unique_ptr
    所管理的对象,而不需要修改其所有权,也不需要修改
    unique_ptr
    本身(比如让它指向别的对象或者释放它),那么最合适的方式是传递一个
    const std::unique_ptr&
    。这种方式效率最高,因为它避免了任何所有权操作,只是提供了一个临时的、只读的访问。

    void observe_object(const std::unique_ptr& ptr) {
        if (ptr) {
            ptr->do_something_const(); // 可以访问对象
            // ptr = nullptr; // 编译错误:不能修改 const 引用
        }
    }
  2. 修改(Modify)

    unique_ptr
    所管理的对象,不转移所有权: 如果函数需要修改
    unique_ptr
    所管理的对象内容,但仍然不涉及所有权转移,可以传递
    std::unique_ptr&
    。这允许函数通过
    ptr->
    操作符修改底层对象。

    void modify_object(std::unique_ptr& ptr) {
        if (ptr) {
            ptr->do_something_non_const(); // 可以修改对象
            // ptr = std::make_unique(); // 也可以修改 unique_ptr 本身,但通常不建议
        }
    }

    值得注意的是,这种方式也可以让函数修改

    unique_ptr
    本身(比如重新赋值或
    release()
    ),但这在语义上通常与“修改底层对象”有所混淆,需要小心使用。我个人更倾向于,如果只是修改底层对象,就直接传递底层对象的引用,例如
    MyClass&
    ,这样语义更清晰。

  3. 转移(Transfer)所有权给函数: 当函数需要完全接管

    unique_ptr
    所管理对象的所有权时,比如一个工厂函数接收一个半成品,然后完成并存储它,此时应该通过值传递
    std::unique_ptr
    。在调用时,你需要显式地使用
    std::move
    来转移所有权。一旦所有权转移,原始的
    unique_ptr
    将变为空。

    void take_ownership(std::unique_ptr ptr) { // 通过值接收
        if (ptr) {
            // 现在这个函数拥有了 MyClass 实例的所有权
            ptr->process_and_store();
        }
        // ptr 在函数结束时自动销毁其管理的对象
    }
    
    // 调用时:
    std::unique_ptr original_ptr = std::make_unique();
    take_ownership(std::move(original_ptr)); // 必须使用 std::move
    // 此时 original_ptr 已经为空
  4. 从函数返回(Return)

    unique_ptr
    当函数创建一个新的对象,并希望将所有权移交给调用者,或者它已经拥有一个对象,现在想把所有权“交出去”时,应该通过值返回
    std::unique_ptr
    。现代C++编译器通常会利用返回值优化(RVO/NRVO)来避免实际的拷贝或移动操作,使其非常高效。

    std::unique_ptr create_my_object() {
        // 创建一个新对象并返回其所有权
        return std::make_unique();
    }
    
    std::unique_ptr process_and_transfer(std::unique_ptr input_ptr) {
        if (input_ptr) {
            input_ptr->do_some_processing();
        }
        // 返回所有权,可能是新的,也可能是修改过的旧的
        return input_ptr; // 编译器通常会优化为移动
    }
    
    // 调用时:
    std::unique_ptr obj1 = create_my_object();
    std::unique_ptr obj2 = process_and_transfer(std::move(obj1));

什么时候应该将
unique_ptr
作为
const&
传递?

在我看来,这是

unique_ptr
作为函数参数最常见、也最推荐的用法之一,尤其是在设计API时。当你有一个函数,它的职责仅仅是“看”一下
unique_ptr
所管理的对象,获取一些信息,或者执行一些不改变对象状态的操作时,
const std::unique_ptr&
就是你的首选。

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

想象一下,你有一个日志记录器,它需要打印某个对象的当前状态。这个日志器显然不应该获得对象的所有权,也不应该修改对象本身,它只是一个旁观者。这时候,传递

const std::unique_ptr&
就完美符合语义。它清晰地表达了“我只是想借用一下你的东西,不会拿走,也不会弄坏”。

class DataProcessor {
public:
    void process_data(const std::unique_ptr& data_source) {
        if (data_source) {
            std::cout << "Processing data: " << *data_source << std::endl;
            // 尝试修改 data_source 会导致编译错误,这很好
            // *data_source = "new data"; // 如果 string 是 const,这里会报错
        } else {
            std::cout << "No data source to process." << std::endl;
        }
    }

    void analyze_data_length(const std::unique_ptr>& numbers) {
        if (numbers) {
            std::cout << "Vector size: " << numbers->size() << std::endl;
            // numbers->push_back(100); // 编译错误,因为 numbers 是 const 引用
        }
    }
};

// 使用示例
int main() {
    auto my_string_ptr = std::make_unique("Hello C++");
    auto my_vector_ptr = std::make_unique>(std::initializer_list{1, 2, 3});

    DataProcessor processor;
    processor.process_data(my_string_ptr);
    processor.analyze_data_length(my_vector_ptr);

    // my_string_ptr 和 my_vector_ptr 仍然有效,所有权未变
    std::cout << "Original string after processing: " << *my_string_ptr << std::endl;
    return 0;
}

这种传递方式的优点在于:

  1. 明确的语义: 清楚地表明函数不会获取所有权,也不会修改
    unique_ptr
    本身。
  2. 效率高: 仅仅传递一个引用,没有额外的内存分配或所有权转移开销。
  3. 安全性: 编译器会强制执行
    const
    约束,防止意外修改。

如果你只是想访问底层对象,甚至可以考虑直接传递底层对象的

const&
(例如
const MyClass&
),这样更解耦,函数甚至不需要知道它处理的是一个
unique_ptr
。但如果你需要检查
unique_ptr
是否为空(即
if (ptr)
),或者函数确实需要
unique_ptr
的类型信息,那么
const std::unique_ptr&
是更合适的选择。

酷兔AI论文
酷兔AI论文

专业原创高质量、低查重,免费论文大纲,在线AI生成原创论文,AI辅助生成论文的神器!

下载

如何通过值传递
unique_ptr
来转移所有权?

当你的函数需要“拿走”一个对象的所有权时,也就是说,这个对象从现在开始由这个函数或函数内部的某个实体来管理其生命周期,那么你就应该通过值传递

std::unique_ptr
。这种场景通常发生在工厂函数、资源管理器或者某个组件需要接管另一个组件创建的资源时。

这种方式的重点在于,当

unique_ptr
作为参数按值传递时,它会触发一个移动构造函数。这意味着原始的
unique_ptr
会将其内部的裸指针“交”给新的
unique_ptr
(函数参数),然后原始的
unique_ptr
会变为空(
nullptr
)。这是一种非常明确且高效的所有权转移机制。

// 假设有一个资源管理类
class ResourceManager {
public:
    // 接收一个 unique_ptr,表示将资源添加到管理器中
    void add_resource(std::unique_ptr resource) {
        if (resource) {
            std::cout << "Resource " << resource->get_id() << " added to manager." << std::endl;
            resources_.push_back(std::move(resource)); // 将所有权转移到 vector 中
        } else {
            std::cout << "Attempted to add a null resource." << std::endl;
        }
    }

    // 假设可以根据ID查找并移除资源,并返回其所有权
    std::unique_ptr remove_resource(int id) {
        for (auto it = resources_.begin(); it != resources_.end(); ++it) {
            if (*it && (*it)->get_id() == id) {
                std::cout << "Resource " << id << " removed from manager." << std::endl;
                std::unique_ptr removed_res = std::move(*it); // 转移所有权
                resources_.erase(it);
                return removed_res;
            }
        }
        std::cout << "Resource " << id << " not found." << std::endl;
        return nullptr; // 如果没找到,返回空指针
    }

private:
    std::vector> resources_;
};

class SomeResource {
public:
    SomeResource(int id) : id_(id) { std::cout << "SomeResource " << id_ << " constructed." << std::endl; }
    ~SomeResource() { std::cout << "SomeResource " << id_ << " destructed." << std::endl; }
    int get_id() const { return id_; }
    void do_work() { std::cout << "Resource " << id_ << " doing work." << std::endl; }
private:
    int id_;
};

int main() {
    ResourceManager manager;

    std::unique_ptr res1 = std::make_unique(101);
    std::unique_ptr res2 = std::make_unique(102);

    std::cout << "Before adding, res1 is " << (res1 ? "valid" : "null") << std::endl;
    manager.add_resource(std::move(res1)); // 必须使用 std::move
    std::cout << "After adding, res1 is " << (res1 ? "valid" : "null") << std::endl; // res1 变为空

    manager.add_resource(std::move(res2)); // res2 也变为空

    std::unique_ptr retrieved_res = manager.remove_resource(101);
    if (retrieved_res) {
        retrieved_res->do_work();
    }

    // retrieved_res 在 main 结束时被销毁
    return 0;
}

关键点:

  • std::move
    的使用:
    这是强制性的。如果你不使用
    std::move
    ,编译器会尝试进行拷贝(而
    unique_ptr
    是不可拷贝的),从而导致编译错误
    std::move
    本质上是将一个左值强制转换为右值引用,从而允许移动语义的发生。
  • 所有权转移: 调用
    std::move
    后,原始的
    unique_ptr
    不再拥有资源,其内部的裸指针会变为
    nullptr
    。这是
    unique_ptr
    独占所有权的特性所决定的,也是其安全性的保障。
  • 效率: 移动操作通常只涉及指针的复制和源指针的置空,效率非常高,几乎没有额外的开销。

从函数返回
unique_ptr
的最佳实践是什么?

从函数返回

std::unique_ptr
是C++中一种非常强大且推荐的模式,尤其是在实现工厂函数(factory function)或者需要将一个资源的所有权从一个作用域传递到另一个作用域时。最佳实践是通过值返回
std::unique_ptr

这种方式的强大之处在于,现代C++编译器通常能够执行所谓的返回值优化(RVO - Return Value Optimization)具名返回值优化(NRVO - Named Return Value Optimization)。这意味着,即使从函数内部返回一个本地创建的

unique_ptr
对象,编译器也可能直接在调用者的内存位置构造这个对象,从而完全避免了移动构造函数的调用,实现了零开销的所有权转移。即使没有RVO/NRVO,也会发生一次高效的移动操作。

class Product {
public:
    Product(int id) : id_(id) { std::cout << "Product " << id_ << " constructed." << std::endl; }
    ~Product() { std::cout << "Product " << id_ << " destructed." << std::endl; }
    void show_info() const { std::cout << "This is Product " << id_ << std::endl; }
private:
    int id_;
};

// 工厂函数:创建一个 Product 对象并返回其所有权
std::unique_ptr create_product(int id) {
    std::cout << "Inside create_product(" << id << ")" << std::endl;
    // 这里创建一个本地的 unique_ptr
    auto p = std::make_unique(id);
    // 编译器会优化这里的返回,通常不会有移动操作
    return p;
}

// 另一个函数,接收一个 unique_ptr,处理后返回一个新的 unique_ptr (或原始的)
std::unique_ptr transform_product(std::unique_ptr original_product, int new_id) {
    std::cout << "Inside transform_product. Original product ID: ";
    if (original_product) {
        std::cout << original_product->id_ << std::endl;
        // 假设我们在这里销毁旧的,创建一个新的
        // 实际上也可以修改 original_product 并返回
    } else {
        std::cout << "null" << std::endl;
    }
    return std::make_unique(new_id); // 返回一个新的产品
}


int main() {
    std::cout << "--- Creating product ---" << std::endl;
    // 调用工厂函数,获取一个 Product 的所有权
    std::unique_ptr my_product = create_product(1);
    if (my_product) {
        my_product->show_info();
    }

    std::cout << "\n--- Transforming product ---" << std::endl;
    // 将 my_product 的所有权转移给 transform_product,并接收新的所有权
    std::unique_ptr transformed_product = transform_product(std::move(my_product), 2);
    // 此时 my_product 已经为空
    if (transformed_product) {
        transformed_product->show_info();
    }

    // transformed_product 在 main 结束时自动销毁
    std::cout << "\n--- End of main ---" << std::endl;
    return 0;
}

为什么是最佳实践?

  1. 清晰的所有权语义: 函数返回
    unique_ptr
    明确地告诉调用者,它将获得一个独占所有权的资源,并且有责任管理这个资源的生命周期。
  2. 安全性: 消除了原始指针可能带来的内存泄漏、双重释放等问题。资源在
    unique_ptr
    的生命周期结束时自动释放。
  3. 效率: 得益于RVO/NRVO,或者至少是高效的移动语义,性能开销极小。这比手动管理原始指针或者使用
    shared_ptr
    (在不需要共享所有权时)更高效。
  4. 避免悬空指针:
    unique_ptr
    被返回并被另一个
    unique_ptr
    接收时,所有权链条是清晰的,不会出现多个指针指向同一块内存但只有其中一个负责释放的情况。

避免返回原始指针(

T*
)来表示所有权,因为这会给调用者带来巨大的负担,他们需要记住何时以及如何释放资源。而返回
shared_ptr
只有在确实需要共享所有权时才考虑,否则会引入不必要的开销。因此,对于独占所有权的场景,通过值返回
unique_ptr
是毫无疑问的最佳选择。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

756

2023.08.22

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

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

525

2023.09.20

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

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

108

2024.02.23

空指针异常处理
空指针异常处理

本专题整合了空指针异常解决方法,阅读专题下面的文章了解更多详细内容。

22

2025.11.16

function是什么
function是什么

function是函数的意思,是一段具有特定功能的可重复使用的代码块,是程序的基本组成单元之一,可以接受输入参数,执行特定的操作,并返回结果。本专题为大家提供function是什么的相关的文章、下载、课程内容,供大家免费下载体验。

479

2023.08.04

js函数function用法
js函数function用法

js函数function用法有:1、声明函数;2、调用函数;3、函数参数;4、函数返回值;5、匿名函数;6、函数作为参数;7、函数作用域;8、递归函数。本专题提供js函数function用法的相关文章内容,大家可以免费阅读。

163

2023.10.07

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

61

2026.01.19

java用途介绍
java用途介绍

本专题整合了java用途功能相关介绍,阅读专题下面的文章了解更多详细内容。

87

2026.01.19

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
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号