乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

unique_ptr

定义与作用

std::unique_ptr(C++11)是独占所有权的智能指针:同一时刻只有一个 unique_ptr 拥有所指向的对象。当 unique_ptr 被销毁或重置时,所拥有的对象被自动 delete。

auto p = std::make_unique<int>(42);
// p 独占这个 int,离开作用域自动释放

核心价值:零额外开销(与裸指针同等大小和性能)+ 独占所有权语义(不可拷贝,只可移动)。

核心原理

unique_ptr 的所有权转移

unique_ptr 的内部结构

完整示例

示例一:对象池管理系统

场景说明:使用 unique_ptr 管理内存中的对象池,确保无泄漏。

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <stdexcept>

// 资源类——不可拷贝,可移动
class Texture {
public:
    Texture(std::string name, size_t size_kb)
        : name_(std::move(name)), size_kb_(size_kb) {
        std::cout << "  [加载] 纹理 " << name_
                  << " (" << size_kb_ << " KB)" << std::endl;
    }

    ~Texture() {
        std::cout << "  [释放] 纹理 " << name_ << std::endl;
    }

    const std::string& name() const { return name_; }
    size_t size() const { return size_kb_; }

    // 禁止拷贝
    Texture(const Texture&) = delete;
    Texture& operator=(const Texture&) = delete;

    // 允许移动
    Texture(Texture&&) noexcept = default;
    Texture& operator=(Texture&&) noexcept = default;

private:
    std::string name_;
    size_t size_kb_;
};

// 纹理管理器——unique_ptr 管理所有权
class TextureManager {
public:
    // 加载纹理——返回裸指针(不转移所有权)
    Texture* load(const std::string& name, size_t size_kb) {
        auto texture = std::make_unique<Texture>(name, size_kb);
        Texture* raw_ptr = texture.get();
        textures_[name] = std::move(texture);
        return raw_ptr;
    }

    // 卸载纹理
    bool unload(const std::string& name) {
        return textures_.erase(name) > 0;
    }

    // 查询纹理
    Texture* get(const std::string& name) {
        auto it = textures_.find(name);
        return it != textures_.end() ? it->second.get() : nullptr;
    }

    // 移动纹理所有权——调用方接管
    std::unique_ptr<Texture> release(const std::string& name) {
        auto it = textures_.find(name);
        if (it == textures_.end()) return nullptr;

        auto ptr = std::move(it->second);
        textures_.erase(it);
        return ptr;
    }

    size_t count() const { return textures_.size(); }
    size_t total_size() const {
        size_t total = 0;
        for (const auto& [_, tex] : textures_)
            total += tex->size();
        return total;
    }

    void print() const {
        std::cout << "  管理器中有 " << count() << " 个纹理 ("
                  << total_size() << " KB):" << std::endl;
        for (const auto& [name, tex] : textures_)
            std::cout << "    " << name << " (" << tex->size() << " KB)" << std::endl;
    }

private:
    std::map<std::string, std::unique_ptr<Texture>> textures_;
};

int main() {
    std::cout << "===== unique_ptr 纹理管理器 =====" << std::endl;

    TextureManager mgr;
    mgr.load("player.png", 256);
    mgr.load("enemy.png", 128);
    mgr.load("background.jpg", 2048);
    mgr.print();

    std::cout << "\n--- 查询 ---" << std::endl;
    Texture* t = mgr.get("player.png");
    if (t) std::cout << "  找到: " << t->name() << std::endl;

    std::cout << "\n--- 释放纹理 ---" << std::endl;
    mgr.unload("enemy.png");
    mgr.print();

    std::cout << "\n--- 转移所有权 ---" << std::endl;
    {
        auto released = mgr.release("player.png");
        if (released) {
            std::cout << "  外部持有: " << released->name() << std::endl;
            mgr.print();
        }
        // 离开作用域 → released 析构 → Texture 自动释放
    }
    std::cout << "  [外部 unique_ptr 已析构]" << std::endl;
    mgr.print();

    std::cout << "\n--- 管理器析构(自动释放剩余纹理)---" << std::endl;
    return 0;
}

预期输出:

===== unique_ptr 纹理管理器 =====
  [加载] 纹理 player.png (256 KB)
  [加载] 纹理 enemy.png (128 KB)
  [加载] 纹理 background.jpg (2048 KB)
  管理器中有 3 个纹理 (2432 KB):
    background.jpg (2048 KB)
    enemy.png (128 KB)
    player.png (256 KB)

--- 查询 ---
  找到: player.png

--- 释放纹理 ---
  [释放] 纹理 enemy.png
  管理器中有 2 个纹理 (2304 KB):
    background.jpg (2048 KB)
    player.png (256 KB)

--- 转移所有权 ---
  外部持有: player.png
  管理器中有 1 个纹理 (2048 KB):
    background.jpg (2048 KB)
  [释放] 纹理 player.png
  [外部 unique_ptr 已析构]
  管理器中有 1 个纹理 (2048 KB):
    background.jpg (2048 KB)

--- 管理器析构(自动释放剩余纹理)---
  [释放] 纹理 background.jpg

逐段分析:

  • std::make_unique<Texture>(name, size) 是创建 unique_ptr 的首选方式——直接构造、无 new 裸露
  • Texture* get() 返回裸指针用于访问,不转移所有权
  • release() 用 std::move 转移所有权给调用方——本管理器不再持有
  • map 中存储 unique_ptr——每个纹理所有权唯一且清晰
  • 管理器析构时,map 中的 unique_ptr 自动析构 → Texture 自动释放

示例二:自定义删除器与 pImpl

#include <iostream>
#include <memory>
#include <cstdio>

// ====== 场景 1:FILE* 用 unique_ptr 管理 ======

struct FileCloser {
    void operator()(std::FILE* fp) const {
        if (fp) {
            std::fclose(fp);
            std::cout << "  文件已关闭" << std::endl;
        }
    }
};

void read_file_with_raii() {
    // unique_ptr + 自定义删除器
    std::unique_ptr<std::FILE, FileCloser> file(
        std::fopen("test.txt", "w"));

    if (!file) {
        std::cerr << "无法打开文件" << std::endl;
        return;
    }

    std::fputs("Hello, unique_ptr!\n", file.get());
    std::cout << "  写入成功" << std::endl;
    // 离开作用域 → FileCloser 自动 fclose
}

// Lambda 删除器(更简洁)
void read_file_with_lambda() {
    auto file_deleter = [](std::FILE* fp) {
        if (fp) {
            std::fclose(fp);
            std::cout << "  [lambda] 文件已关闭" << std::endl;
        }
    };

    std::unique_ptr<std::FILE, decltype(file_deleter)> file(
        std::fopen("test2.txt", "w"), file_deleter);

    if (file) {
        std::fputs("Hello, lambda deleter!\n", file.get());
    }
}

// ====== 场景 2:pImpl 惯用法 ======

// 头文件中的接口(pImpl 隐藏实现细节)
class Widget {
public:
    Widget();
    ~Widget();                          // 必须在 .cpp 定义(Impl 是不完整类型)
    Widget(Widget&&) noexcept;          // 移动语义
    Widget& operator=(Widget&&) noexcept;

    Widget(const Widget&) = delete;     // 简化:禁止拷贝
    Widget& operator=(const Widget&) = delete;

    void doWork();
    int value() const;

private:
    struct Impl;                        // 前向声明——不暴露实现细节
    std::unique_ptr<Impl> pImpl_;
};

// .cpp 文件中的实现
struct Widget::Impl {
    int data = 42;
    std::string name = "WidgetImpl";

    void work() {
        std::cout << "  Impl::work() → data=" << data
                  << ", name=" << name << std::endl;
    }
};

Widget::Widget() : pImpl_(std::make_unique<Impl>()) {
    std::cout << "  Widget 构造" << std::endl;
}

// 析构函数必须在 .cpp 中定义(此时 Impl 已是完整类型)
Widget::~Widget() = default;

Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;

void Widget::doWork() { pImpl_->work(); }

int Widget::value() const { return pImpl_->data; }

int main() {
    std::cout << "===== 自定义删除器 =====" << std::endl;
    read_file_with_raii();
    read_file_with_lambda();

    std::cout << "\n===== pImpl 惯用法 =====" << std::endl;
    Widget w;
    w.doWork();
    std::cout << "  value: " << w.value() << std::endl;

    // 移动语义
    Widget w2 = std::move(w);
    w2.doWork();

    return 0;
}

预期输出:

===== 自定义删除器 =====
  写入成功
  文件已关闭
  [lambda] 文件已关闭

===== pImpl 惯用法 =====
  Widget 构造
  Impl::work() → data=42, name=WidgetImpl
  value: 42
  Impl::work() → data=42, name=WidgetImpl

逐段分析:

  • 自定义删除器让 unique_ptr 管理任意资源(FILE*、socket、OS handle)
  • Lambda 删除器比仿函数更简洁——用 decltype 获取类型
  • pImpl(Pointer to Implementation)用 unique_ptr 管理实现类——接口与实现完全分离,缩短编译时间
  • 析构函数必须在 Impl 完整定义的 .cpp 中——否则 unique_ptr 无法调用 Impl 的析构函数

易错场景与面试考点

易错场景

场景错误表现正确做法
拷贝 unique_ptr编译错误用 std::move 转移所有权
多个 unique_ptr 指向同对象双重释放unique_ptr 保证独占
从 unique_ptr 取裸指针后释放 unique_ptr悬挂指针理解所有权转移时机
pImpl 的析构函数在头文件编译错误(不完整类型)析构函数定义在 .cpp
用 new 创建 unique_ptr异常不安全优先用 make_unique

常见面试问题

  1. unique_ptr 和 shared_ptr 的核心区别?——unique_ptr 独占所有权(不可拷贝,只可移动),零额外开销(与裸指针同等大小)。shared_ptr 共享所有权(引用计数),有控制块开销。

  2. 为什么推荐 make_unique 而非 new?——(1) 异常安全:f(make_unique<T>(), make_unique<T>()) 不会泄漏;(2) 代码简洁:不需要重复类型名;(3) C++14 才引入,但可自行实现。

  3. unique_ptr 如何支持自定义删除器?——作为第二个模板参数:unique_ptr<T, Deleter>。删除器是类型的一部分——函数指针删除器会比无状态删除器大。

  4. pImpl 惯用法中为什么析构函数必须在 .cpp 定义?——unique_ptr 析构时需要完整类型的定义来调用 delete。头文件中 Impl 只有前向声明(不完整类型),所以析构函数必须放在 Impl 定义之后的 .cpp 文件中。

  5. unique_ptr 可以用于数组吗?——可以:unique_ptr<T[]>。支持 operator[],但没有 operator* 和 operator->。通常推荐用 std::vector 或 std::array 代替。

小结

  • unique_ptr:独占所有权、不可拷贝只可移动、零额外开销
  • make_unique<T>(args...) 是创建首选——异常安全、代码简洁
  • 自定义删除器扩展 unique_ptr 到任意资源类型(FILE*、socket 等)
  • pImpl 惯用法依赖 unique_ptr 实现编译期封装
  • 所有权转移用 std::move,返回裸指针用 get(),释放所有权用 release()
上一页
RAII 惯用法
下一页
shared_ptr 与 weak_ptr