首页 > 后端开发 > C++ > 正文

为什么现代C++推荐使用std::make_unique来创建unique_ptr

P粉602998670
发布: 2025-09-10 08:19:01
原创
988人浏览过
推荐使用std::make_unique创建unique_ptr,因其将对象构造与智能指针创建封装为原子操作,避免因函数参数求值顺序不确定导致的异常安全问题,同时提升代码简洁性与可读性。

为什么现代c++推荐使用std::make_unique来创建unique_ptr

现代C++中推荐使用

std::make_unique
登录后复制
来创建
unique_ptr
登录后复制
,这主要是因为它能有效提升代码的异常安全性,同时让代码更简洁、易读。直接使用
new
登录后复制
来构造
unique_ptr
登录后复制
,在某些复杂的表达式中,可能会引入难以察觉的资源泄露风险。

解决方案

当我们谈论

unique_ptr
登录后复制
的创建,很多人可能习惯性地写成
std::unique_ptr<MyClass> ptr(new MyClass());
登录后复制
。这在大多数简单场景下看起来没问题,但实际上,这种写法在某些特定情况下存在一个微妙但重要的缺陷,那就是潜在的异常安全性问题。

考虑一个函数调用,其中包含多个参数,例如:

some_function(std::unique_ptr<MyClass>(new MyClass()), another_function_that_might_throw());
登录后复制

C++标准对函数参数的求值顺序并没有严格规定,只知道在调用

some_function
登录后复制
之前,所有的参数都必须被求值完毕。这意味着,编译器可能会以以下某种顺序执行操作:

  1. 调用
    new MyClass()
    登录后复制
    分配内存并构造对象。
  2. 调用
    another_function_that_might_throw()
    登录后复制
  3. 调用
    std::unique_ptr<MyClass>(...)
    登录后复制
    构造智能指针,接管
    MyClass
    登录后复制
    对象的管理。

如果执行顺序是1 -> 2 -> 3,并且

another_function_that_might_throw()
登录后复制
在步骤2中抛出了异常,那么步骤3(
unique_ptr
登录后复制
的构造)将永远不会发生。此时,步骤1中通过
new MyClass()
登录后复制
分配的内存和构造的对象将无人管理,从而导致内存泄露。因为
new
登录后复制
操作已经完成,但
unique_ptr
登录后复制
还没来得及接管。

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

std::make_unique
登录后复制
的设计,正是为了解决这个问题。它将对象的分配和
unique_ptr
登录后复制
的构造封装成一个原子操作。当你写
std::make_unique<MyClass>()
登录后复制
时,
MyClass
登录后复制
对象的创建和
unique_ptr
登录后复制
对它的接管,要么一起成功,要么一起失败,中间不会留下悬空的原始指针。这保证了在存在异常的情况下,资源能够得到妥善管理,避免了上述的泄露风险。它强制了“分配和拥有”这两个动作的紧密耦合。

std::make_unique是如何提供异常安全性的?

std::make_unique
登录后复制
提供异常安全性的核心机制在于它将内存分配(
new
登录后复制
)和
unique_ptr
登录后复制
的构造视为一个单一的、不可分割的操作。我们之前提到的潜在泄露场景,其根本原因在于C++标准允许编译器在求值函数参数时,对子表达式的求值顺序有一定自由度。

例如,对于

func(A(), B(), C())
登录后复制
这样的调用,A、B、C的求值顺序是不确定的。如果A是
new MyObject()
登录后复制
,B是
another_risky_call()
登录后复制
,C是
std::unique_ptr<MyObject>(...)
登录后复制
,那么在
new MyObject()
登录后复制
执行完毕后,
another_risky_call()
登录后复制
可能在
std::unique_ptr
登录后复制
构造前抛出异常,导致
MyObject
登录后复制
泄露。

std::make_unique
登录后复制
通过在内部完成
new
登录后复制
操作并直接返回一个已构造好的
unique_ptr
登录后复制
实例,绕开了这个“参数求值顺序不确定”的陷阱。当调用
std::make_unique<MyClass>(args...)
登录后复制
时,它会:

  1. 在内部调用
    new MyClass(args...)
    登录后复制
    来分配内存并构造对象。
  2. 立即将这个新创建的原始指针传递给
    unique_ptr
    登录后复制
    的构造函数。 整个过程被封装在一个函数调用中,使得从外部看来,
    make_unique
    登录后复制
    要么返回一个有效的
    unique_ptr
    登录后复制
    ,要么在内部抛出异常(例如,如果
    MyClass
    登录后复制
    的构造函数抛出),但绝不会在
    new
    登录后复制
    了一个对象后,又因为外部其他操作的异常而导致该对象无人管理。

这使得像下面这样的代码变得安全:

void process_data(std::unique_ptr<Data> p, int value);
void potentially_failing_operation();

// 不安全的方式
// process_data(std::unique_ptr<Data>(new Data()), potentially_failing_operation());
// 如果 Data 的 new 完成了,但 potentially_failing_operation() 抛出,Data 会泄露。

// 安全的方式
process_data(std::make_unique<Data>(), potentially_failing_operation());
// make_unique 确保 Data 对象要么被 unique_ptr 拥有,要么在创建过程中失败,不会出现中间状态的泄露。
登录后复制

这种设计极大地简化了对异常安全性的考量,让开发者能更专注于业务逻辑,而不是底层内存管理的复杂性。

除了异常安全,使用std::make_unique还有哪些实际好处?

除了异常安全性这个核心优势,

std::make_unique
登录后复制
在日常编码中还带来了不少其他实际的好处,让代码更具可读性和维护性。

无阶未来模型擂台/AI 应用平台
无阶未来模型擂台/AI 应用平台

无阶未来模型擂台/AI 应用平台,一站式模型+应用平台

无阶未来模型擂台/AI 应用平台35
查看详情 无阶未来模型擂台/AI 应用平台

首先,代码的简洁性和可读性得到了显著提升。对比一下两种创建方式:

// 旧方式
std::unique_ptr<MyComplexType> ptr1(new MyComplexType(arg1, arg2, arg3));

// 推荐方式
std::unique_ptr<MyComplexType> ptr2 = std::make_unique<MyComplexType>(arg1, arg2, arg3);
登录后复制

很明显,第二种方式更加简洁。它避免了重复写入类型名称

MyComplexType
登录后复制
,减少了冗余,也使得代码的意图——“创建一个
MyComplexType
登录后复制
的唯一所有权智能指针”——更加清晰。这种“不重复自己”(DRY原则)的体现,在类型名称较长或模板类型时尤其明显。

其次,它与

std::make_shared
登录后复制
保持了风格上的一致性。在现代C++中,
std::make_shared
登录后复制
是创建
std::shared_ptr
登录后复制
的推荐方式,其原因也包括异常安全性和潜在的性能优化(通过一次内存分配同时为对象和控制块分配内存)。
std::make_unique
登录后复制
的引入,使得智能指针的创建方式趋于统一,降低了学习曲线,也让代码库看起来更加协调和专业。

虽然对于

unique_ptr
登录后复制
而言,
make_unique
登录后复制
在性能上的优势通常不如
make_shared
登录后复制
那么显著(
unique_ptr
登录后复制
没有独立的控制块),但编译器和库实现者仍然有可能在某些情况下,通过优化
make_unique
登录后复制
的内部实现,实现微小的性能提升,例如减少内存分配器的调用次数。但这通常不是选择
make_unique
登录后复制
的主要驱动因素,其主要价值仍在异常安全性和代码清晰度上。

在哪些情况下,我可能仍然需要直接使用new来创建unique_ptr?

尽管

std::make_unique
登录后复制
是创建
unique_ptr
登录后复制
的首选方式,但在某些特定场景下,我们仍然需要或更适合直接使用
new
登录后复制
操作符来构造
unique_ptr
登录后复制
。这些情况通常涉及更高级的内存管理需求或与C风格API的交互。

最常见的情况是当需要使用自定义deleter时

std::make_unique
登录后复制
不提供直接指定自定义deleter的接口。
unique_ptr
登录后复制
的构造函数有一个重载版本,允许你传入一个原始指针和一个deleter对象或函数指针。 例如,如果你想管理一个C语言的
FILE*
登录后复制
,并确保它被
fclose
登录后复制
正确关闭:

#include <cstdio>
#include <memory>

// 自定义 deleter 函数
void file_closer(FILE* f) {
    if (f) {
        fclose(f);
    }
}

int main() {
    // 无法使用 make_unique
    // std::unique_ptr<FILE, decltype(&file_closer)> log_file = std::make_unique<FILE>(fopen("log.txt", "w"), &file_closer); // 错误

    // 必须直接使用 new (或者这里是 fopen 返回的指针)
    std::unique_ptr<FILE, decltype(&file_closer)> log_file(fopen("log.txt", "w"), &file_closer);
    if (log_file) {
        fprintf(log_file.get(), "Hello from unique_ptr!\n");
    }
    // log_file 在离开作用域时会自动关闭文件
    return 0;
}
登录后复制

这里,

fopen
登录后复制
返回的是一个原始
FILE*
登录后复制
指针,我们需要
unique_ptr
登录后复制
来接管它,并指定
file_closer
登录后复制
作为其析构时的操作。

另一个场景是当需要从一个已存在的原始指针接管所有权时。这可能发生在与遗留C++代码库交互,或者当一个工厂函数返回一个

new
登录后复制
出来的原始指针时。

// 假设这是一个C风格的API,返回一个 new 出来的对象
MyClass* create_my_class_raw() {
    return new MyClass();
}

int main() {
    MyClass* raw_ptr = create_my_class_raw();
    // 此时不能用 make_unique,因为它会再次 new 一个对象
    std::unique_ptr<MyClass> managed_ptr(raw_ptr);
    // managed_ptr 现在拥有了 raw_ptr 指向的对象
    return 0;
}
登录后复制

在这种情况下,我们不是要“创建”一个新的对象,而是要“接管”一个已经存在的对象的所有权,所以直接将原始指针传递给

unique_ptr
登录后复制
的构造函数是唯一的选择。

最后,当处理

unique_ptr
登录后复制
管理数组且需要自定义deleter时
std::make_unique
登录后复制
有一个重载版本用于创建数组(
std::make_unique<T[]>(size)
登录后复制
),它会使用
delete[]
登录后复制
来释放内存。但如果你需要一个特殊的数组deleter,比如一个内存池的释放函数,你就需要直接
new T[size]
登录后复制
并结合自定义deleter来构造
unique_ptr<T[], MyCustomArrayDeleter>
登录后复制

这些情况虽然相对小众,但确实存在,提醒我们

std::make_unique
登录后复制
并非万能,理解其局限性与优势同样重要。选择哪种方式,最终还是取决于具体的编程需求和上下文。

以上就是为什么现代C++推荐使用std::make_unique来创建unique_ptr的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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