C++ std::make_unique详解-安全创建unique_ptr的官方方法

C++ std::make_unique详解-安全创建unique_ptr的官方方法

C++ std::make_unique详解-安全创建unique_ptr的官方方法

一、C++ std::make_unique详解

1、什么是 std::make_unique

std::make_unique 是 C++14 标准库中引入的一个模板函数,用于创建并返回一个指向动态分配对象的 std::unique_ptr 智能指针。它的主要目的是提供一种更安全、更简洁的方式来创建和管理独占所有权的动态对象。

2、为什么需要 std::make_unique? (与直接 new 对比)

std::make_unique 出现之前,创建 std::unique_ptr 通常是这样做的:

std::unique_ptr<MyClass>ptr(newMyClass(arg1, arg2));

这种方式存在一些潜在问题:

  • 异常安全风险: 如果在 new 操作和 unique_ptr 构造之间发生了异常(例如在计算构造函数参数时),那么 new 分配的内存可能无法被 unique_ptr 接管,从而发生内存泄漏。
  • 代码冗余: 需要重复书写类型 MyClass
  • 风格一致性: C++11 提供了 std::make_shared 来创建 std::shared_ptr,缺少 std::make_unique 显得不完整。

std::make_unique 解决了这些问题:

auto ptr = std::make_unique<MyClass>(arg1, arg2);
  • 异常安全:std::make_unique 在内部一次性完成内存分配和对象构造。如果构造函数参数的计算过程中抛出异常,或者构造函数本身抛出异常,因为此时还没有返回 unique_ptr,所以不会有内存泄漏(分配的内存会被自动释放)。
  • 简洁性: 使用 auto 关键字,避免了类型重复书写,代码更简洁。
  • 一致性:std::make_shared 的使用方式保持一致。
  • 效率: 编译器有机会进行优化。

3、基本语法和用法

std::make_unique 有两种主要形式:

    • 数组版本返回的是 std::unique_ptr<T[]>,这与指向单个对象的 std::unique_ptr<T> 是不同的特化。
    • 数组元素会被值初始化(基本类型初始化为 0,类类型调用默认构造函数)。无法在创建数组时指定每个元素的构造参数。
    • 访问数组元素使用 operator[]arr[i] = 42;

创建动态数组:

template<typenameT> std::unique_ptr<T[]> std::make_unique(std::size_t size);

用法:

// 创建一个包含 10 个默认构造的 int 的动态数组auto arr = std::make_unique<int[]>(10);// 创建一个包含 5 个默认构造的 Widget 的动态数组auto widgetArr = std::make_unique<Widget[]>(5);

注意:

创建单个对象:

template<typenameT,typename... Args> std::unique_ptr<T> std::make_unique(Args&&... args);

用法:

// 创建默认构造的 Widgetauto widget1 = std::make_unique<Widget>();// 创建带参数的 Widgetauto widget2 = std::make_unique<Widget>(100,"Hello");// 创建派生类对象 (多态)auto derived = std::make_unique<Derived>(some_arg); std::unique_ptr<Base> basePtr = std::move(derived);// 所有权转移

4、关键特性

  • 所有权独占:std::make_unique 创建的 std::unique_ptr 拥有对所创建对象的独占所有权。这意味着同一时刻只有一个 unique_ptr 可以指向该对象。所有权可以通过 std::move 转移。
  • 自动内存管理:unique_ptr 被销毁(例如离开作用域)时,它所管理的对象会被自动删除(调用其析构函数并释放内存)。
  • 不支持自定义删除器:std::make_unique不支持指定自定义删除器。如果需要自定义删除器,必须直接使用 std::unique_ptr 的构造函数,例如 std::unique_ptr<T, D> ptr(new T, custom_deleter);
  • 不能用于 std::shared_ptrstd::make_unique 只创建 std::unique_ptr。要创建 std::shared_ptr,应使用 std::make_shared

5、总结与建议

  • 优先使用 std::make_unique 在 C++14 及以后的代码中,优先使用 std::make_unique 来创建 std::unique_ptr。它提供了更好的异常安全性和更简洁的代码。
  • 理解所有权: 清楚 unique_ptr 的独占所有权语义,并通过 std::move 来转移所有权。
  • 数组特化: 需要创建动态数组时,使用 std::make_unique<T[]>(size) 形式。
  • 自定义删除器的限制: 如果需要自定义删除器,则不能使用 std::make_unique

二、示例代码

1、示例代码1

#include<iostream>#include<memory>#include<string>classMyClass{public:MyClass(int id, std::string name):id_(id),name_(std::move(name)){ std::cout <<"MyClass constructed: "<< id_ <<", "<< name_ <<"\n";}~MyClass(){ std::cout <<"MyClass destroyed: "<< id_ <<", "<< name_ <<"\n";}voidprint()const{ std::cout <<"ID: "<< id_ <<", Name: "<< name_ <<"\n";}private:int id_; std::string name_;};intmain(){// 创建单个对象auto obj = std::make_unique<MyClass>(1,"Alice"); obj->print();// 创建动态数组 (5 个 int)auto numbers = std::make_unique<int[]>(5);for(int i =0; i <5;++i){ numbers[i]= i * i; std::cout << numbers[i]<<" ";} std::cout <<"\n";// 所有权转移auto newOwner = std::move(obj);if(!obj){ std::cout <<"obj is now nullptr\n";} newOwner->print();// 离开作用域时,newOwner 和 numbers 管理的对象会被自动销毁return0;}

输出:

MyClass constructed: 1, Alice ID: 1, Name: Alice 0 1 4 9 16 obj is now nullptr ID: 1, Name: Alice MyClass destroyed: 1, Alice 
在这里插入图片描述

2、示例代码2

#include<memory>// 包含 std::make_unique 和 std::unique_ptr#include<iostream>// 定义一个简单的自定义类classPoint{public:Point(int x,int y):x_(x),y_(y){}voidprint()const{ std::cout <<"Point("<< x_ <<", "<< y_ <<")\n";}private:int x_;int y_;};intmain(){// 示例 1: 创建一个 std::unique_ptr 指向 int 类型auto num_ptr = std::make_unique<int>(42);// 使用 std::make_unique 分配内存 std::cout <<"整数值: "<<*num_ptr << std::endl;// 解引用指针输出值// 示例 2: 创建一个 std::unique_ptr 指向自定义类 Pointauto point_ptr = std::make_unique<Point>(3,4);// 传递构造函数参数 point_ptr->print();// 调用成员函数return0;}

输出:

整数值:42Point(3,4)
在这里插入图片描述


在这里插入图片描述

Read more

python之路并不一马平川:带你踩坑Pandas

python之路并不一马平川:带你踩坑Pandas

这是我的亲身经历。作为一名全能型的混子,Pandas是我吃饭的家伙之一,但光是把它请到我的电脑上,就差点让我“饭碗不保”。这是一段长达数周,充满挫折、困惑和最终解脱的曲折历程。我将带你完整回顾我踩过的每一个坑,以及那最后的“救命稻草”。我将以第一视角,带你完整回顾我踩过的那些坑,以及我是如何一步步爬出来的。 记得刚入行那年,我接手的第一个项目是个电商小程序开发。当时为了赶进度,我直接跳过了需求分析阶段,结果上线后发现支付接口和后台数据对不上,不得不紧急下架整改。那三天三夜不眠不休的debug经历,现在想起来还心有余悸。 去年在开发智能家居App时,我又犯了个典型错误:没有做好版本兼容性测试。当用户反馈老型号设备无法连接时,我们才发现蓝牙协议栈对新老设备的处理方式完全不同。这个教训让我养成了建立完整测试矩阵的习惯。 最惨痛的经历是去年年底的云服务迁移。当时为了节省成本,我选择了直接全量迁移数据库,结果因为网络波动导致数据不一致,差点酿成重大事故。现在我做数据迁移时都会严格遵循"全量备份-增量同步-数据校验"的标准流程。 这些血泪教训让我明白,在技术这条路上,捷径往往是最远的路。每

By Ne0inhk
【Python】基础语法入门(一)

【Python】基础语法入门(一)

前言 Python作为一门入门门槛低、生态丰富的编程语言,Python早已成为编程初学者、数据分析从业者、后端开发者的首选工具之一。而掌握Python的第一步,就是吃透最核心的基础语法,常量与表达式、变量与类型、注释、输入输出及运算符。今天,我们就结合实例,手把手带你入门这些必备知识点,助你快速搭建Python语法框架。 一、常量与表达式 刚接触 Python 时,我们可以先把它当作一个功能强大的计算器 ,通过简单的表达式,以完成各类算术运算,比如简单的加减乘除,甚至复杂的乘方运算,都能直接通过“表达式”实现。 核心知识点: 1. 表达式与常量:形如1 + 2 * 3的算式称为“表达式”,运算结果为“表达式的返回值”;1、2、3这类固定值称为“字面值常量”,+、-、*、/则是“运算符”。 2. 运算规则:遵循“先乘除后加减”的数学逻辑,

By Ne0inhk
【C++ 入门】:引用、内联函数与 C++11 新特性(auto、范围 for、nullptr)全解析

【C++ 入门】:引用、内联函数与 C++11 新特性(auto、范围 for、nullptr)全解析

目录 一、引用 1.1 引用概念 1.2 引用的特性 1.3 常引用 1.4 使用场景 1.5. 传引用、传值效率比较 1.6  指针和引用的区别 【面试题】:引用和指针的对比 二、内联函数 2.1 内联函数是啥? 2.2 如何判断是否为内联函数? 2.3 内联函数特性 【问题】: 为啥内联函数可能会导致目标文件变大 【问题】:递归不能内联的核心原因 【面试题】:宏的优缺点? 【面试题】:内联函数的优缺点? 三、auto关键字(C++11) 3.1 auto

By Ne0inhk
C++之基于正倒排索引的Boost搜索引擎项目usuallytool部分代码及详解

C++之基于正倒排索引的Boost搜索引擎项目usuallytool部分代码及详解

这部分是通用工具部分的代码,简单来说就是这份代码里面的函数会在项目的其他多个部分里面被使用,所以我们专门创建一个部分用来存储这些代码。 1.FileUtil 这个类就是专门用来读取文件用的,这个代码从指定的文件路径读取文件内容,将读取到的内容(按行读取)追加到传入的字符串指针(out)所指向的字符串中;同时,该方法会返回一个布尔值,用于标识读取操作是否成功 —— 若文件成功打开并完成读取,返回 true;若文件打开失败(如路径错误等),则输出错误信息并返回 false。 文件以二进制输入模式打开,读取过程中不会修改原文件内容。 class FileUtil{ public: static bool ReadFile(const std::string &file_path,std::string *out) { //下面这行代码就是在打开文件,并通过ifstream定义一个对象in,用于关联特定的文件 std::ifstream in(file_path,std::ios::in

By Ne0inhk