C++11 std::unique_ptr — 独占所有权的智能指针

std::unique_ptr --- 独占所有权的智能指针

知识点简介

std::unique_ptr 是 C++11 引入的智能指针,用于管理独占所有权 的动态分配对象。同一时刻只能有一个 unique_ptr 拥有某个裸指针,当 unique_ptr 被销毁时,它所管理的对象也会被自动释放。这使得资源管理变得异常简单------你几乎再也不用手动写 delete 了。

unique_ptr 的核心思想是 RAII(Resource Acquisition Is Initialization):资源在构造时获取,在析构时释放。它的大小和裸指针相同(无额外开销),是零成本抽象(zero-cost abstraction)的典范。

核心语法 / 用法说明

1. 创建 unique_ptr

cpp 复制代码
#include <memory>

// C++14 起推荐使用 std::make_unique(异常安全 + 更简洁)
auto p1 = std::make_unique<int>(42);            // 创建一个 int,值为 42

// C++11 兼容写法(需要显式 new)
std::unique_ptr<int> p2(new int(100));

// 管理数组(C++14 起可用 make_unique 创建数组)
auto arr = std::make_unique<int[]>(10);          // 创建长度为 10 的 int 数组

2. 所有权转移

cpp 复制代码
auto p1 = std::make_unique<int>(42);
auto p2 = std::move(p1);   // 所有权从 p1 转移到 p2
// 此后 p1 == nullptr,p2 拥有该对象

3. 释放与重置

cpp 复制代码
auto p = std::make_unique<int>(100);

int* raw = p.release();    // 放弃所有权,返回裸指针,你必须手动 delete
delete raw;                // 由于 release 了,必须手动释放

p.reset(new int(200));     // 释放旧对象,接管新对象
p.reset();                 // 直接释放,p 变为 nullptr

4. 自定义删除器

cpp 复制代码
// 自定义删除器:关闭文件
auto file_deleter = [](FILE* f) {
    if (f) {
        std::fclose(f);
        std::cout << "文件已关闭\n";
    }
};

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

代码示例

cpp 复制代码
#include <iostream>
#include <memory>
#include <vector>
#include <string>

// ============================================================
// 示例 1:unique_ptr 基本用法 --- 管理动态对象
// ============================================================
class Widget {
public:
    Widget(int id) : m_id(id) {
        std::cout << "Widget #" << m_id << " 被构造\n";
    }
    ~Widget() {
        std::cout << "Widget #" << m_id << " 被析构\n";
    }
    void doWork() const {
        std::cout << "Widget #" << m_id << " 正在工作...\n";
    }
private:
    int m_id;
};

void basic_usage() {
    std::cout << "=== 示例 1:基本用法 ===\n";
    // 工厂函数返回 unique_ptr --- 调用者获得独占所有权
    auto createWidget = [](int id) -> std::unique_ptr<Widget> {
        return std::make_unique<Widget>(id);
    };

    auto w = createWidget(1);   // 创建 Widget #1
    w->doWork();                // 通过 -> 访问成员
    // w 离开作用域时自动析构 Widget #1,无需手动 delete
}

// ============================================================
// 示例 2:容器中存放 unique_ptr --- 多态场景
// ============================================================
class Shape {
public:
    virtual ~Shape() = default;              // 虚析构函数是必须的!
    virtual void draw() const = 0;
    virtual std::string name() const = 0;
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "  ○ 绘制圆形\n";
    }
    std::string name() const override { return "Circle"; }
};

class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "  □ 绘制矩形\n";
    }
    std::string name() const override { return "Rectangle"; }
};

void polymorphic_container() {
    std::cout << "\n=== 示例 2:多态容器 ===\n";
    std::vector<std::unique_ptr<Shape>> shapes;

    // 向容器中添加不同类型的 Shape
    shapes.push_back(std::make_unique<Circle>());
    shapes.push_back(std::make_unique<Rectangle>());
    shapes.push_back(std::make_unique<Circle>());

    // 统一遍历绘制 --- 多态调用
    for (const auto& shape : shapes) {
        shape->draw();
    }

    // 将容器中的元素转移给另一个容器
    std::vector<std::unique_ptr<Shape>> otherShapes = std::move(shapes);
    std::cout << "原容器大小: " << shapes.size()      // 输出 0
              << ", 新容器大小: " << otherShapes.size() // 输出 3
              << "\n";
    // 注意:shapes 现在为空,所有元素已转移
}

// ============================================================
// 示例 3:自定义删除器 --- 管理非内存资源
// ============================================================
struct Connection {
    int id;
    bool isOpen = true;
};

// 模拟打开数据库连接
Connection* openConnection(int id) {
    std::cout << "  打开连接 #" << id << "\n";
    return new Connection{id, true};
}

// 模拟关闭数据库连接
void closeConnection(Connection* conn) {
    if (conn) {
        std::cout << "  关闭连接 #" << conn->id << "\n";
        conn->isOpen = false;
        delete conn;
    }
}

void custom_deleter_demo() {
    std::cout << "\n=== 示例 3:自定义删除器 ===\n";
    {
        // 指定删除器为 closeConnection 函数
        std::unique_ptr<Connection, decltype(&closeConnection)>
            conn(openConnection(42), closeConnection);
        std::cout << "  连接 #" << conn->id << " 状态: "
                  << (conn->isOpen ? "开启" : "关闭") << "\n";
    } // 离开作用域时自动调用 closeConnection
    std::cout << "  连接已自动关闭\n";
}

// ============================================================
// 示例 4:unique_ptr 与工厂模式
// ============================================================
enum class VehicleType { Car, Bike };

class Vehicle {
public:
    virtual ~Vehicle() = default;
    virtual void drive() const = 0;
};

class Car : public Vehicle {
public:
    void drive() const override { std::cout << "  🚗 驾驶汽车\n"; }
};

class Bike : public Vehicle {
public:
    void drive() const override { std::cout << "  🚲 骑自行车\n"; }
};

// 工厂函数:根据类型返回不同的 Vehicle
// 返回 unique_ptr<Vehicle> 明确表达了"调用者获得所有权"的语义
std::unique_ptr<Vehicle> createVehicle(VehicleType type) {
    switch (type) {
        case VehicleType::Car:
            return std::make_unique<Car>();
        case VehicleType::Bike:
            return std::make_unique<Bike>();
        default:
            return nullptr;   // 可以返回空指针
    }
}

void factory_demo() {
    std::cout << "\n=== 示例 4:工厂模式 ===\n";
    auto car = createVehicle(VehicleType::Car);
    if (car) car->drive();

    auto bike = createVehicle(VehicleType::Bike);
    if (bike) bike->drive();
}

// ============================================================
// 主函数
// ============================================================
int main() {
    basic_usage();
    polymorphic_container();
    custom_deleter_demo();
    factory_demo();

    std::cout << "\n所有 unique_ptr 已自动析构,无内存泄漏!\n";
    return 0;
}

编译与运行

bash 复制代码
g++ -std=c++17 -Wall -Wextra -o unique_ptr_demo unique_ptr_demo.cpp
./unique_ptr_demo

预期输出

ini 复制代码
=== 示例 1:基本用法 ===
Widget #1 被构造
Widget #1 正在工作...
Widget #1 被析构

=== 示例 2:多态容器 ===
  ○ 绘制圆形
  □ 绘制矩形
  ○ 绘制圆形
原容器大小: 0, 新容器大小: 3

=== 示例 3:自定义删除器 ===
  打开连接 #42
  连接 #42 状态: 开启
  关闭连接 #42
  连接已自动关闭

=== 示例 4:工厂模式 ===
  🚗 驾驶汽车
  🚲 骑自行车

所有 unique_ptr 已自动析构,无内存泄漏!

注意事项 / 常见陷阱

陷阱 说明 正确做法
禁止拷贝 unique_ptr 的拷贝构造和拷贝赋值都是 = delete 的,因为它是独占所有权 使用 std::move() 转移所有权
不要与裸指针混用 如果你把同一个裸指针交给两个 unique_ptr,会发生 double-free 始终通过 make_uniquenew 直接初始化
基类需要虚析构函数 unique_ptr<Base> 持有 Derived 对象时,如果 Base 没有虚析构,派生类的析构函数不会被调用 给基类加上 virtual ~Base() = default;
数组用 T[] 特化 unique_ptr<int> 调用 deleteunique_ptr<int[]> 调用 delete[] 使用 std::make_unique<int[]>(n)unique_ptr<int[]>(new int[n])
不要在函数参数中按值传递 unique_ptr void foo(unique_ptr<T> p) 会强制调用方转移所有权,语义不清晰 如果只是想访问对象,传 T&T*;如果确实要转移所有权,传 unique_ptr<T>&&
release 后必须手动释放 p.release() 放弃所有权但不释放资源,返回的裸指针由你负责 绝大多数情况用 reset() 而非 release()

实际应用场景

  1. 工厂模式 :工厂函数返回 unique_ptr<Base>,调用者获得独占所有权,语义清晰且安全。

  2. PIMPL 惯用法(Pointer to Implementation) :用 unique_ptr 持有实现类,隐藏实现细节,减少编译依赖。这是 C++ 大型项目中减少编译时间的核心技巧。

  3. 容器中的多态对象vector<unique_ptr<Base>> 可以安全地存放不同类型的派生类对象,离开作用域自动释放。

  4. RAII 资源管理:配合自定义删除器,可以管理文件句柄、数据库连接、套接字、锁等非内存资源,统一用 RAII 的方式管理。

  5. 所有权传递 :在多线程场景中,用 unique_ptr 将对象的所有权从一个线程安全地转移到另一个线程(配合 std::move),避免共享状态带来的同步问题。


今日小结unique_ptr 是现代 C++ 资源管理的基石。记住一个原则------能用 unique_ptr 就不用 shared_ptr ,能用智能指针就不用裸指针。明天我们将继续深入,探讨 shared_ptrweak_ptr 的共享所有权与循环引用问题。

相关推荐
宏笋1 小时前
一款集结古今中外文学查阅与编辑的软件
qt
机器视觉知识推荐、就业指导12 小时前
为什么老说“纯 Qt 没啥就业市场”?
开发语言·qt
telepan12 小时前
Qt 开发避坑与性能指南:如何优雅、安全地定义全局常量字符串?
开发语言·qt
小灰灰搞电子12 小时前
完全驾驭 Qt 与数据库:C++ ORM 框架 QxOrm 原理与实践指南
数据库·c++·qt
QT界面美化性能优化12 小时前
QT+AI:使用AI技术为QT应用程序赋能
c++·人工智能·qt·opencv·qt教程·qt6.3
m0_6952514912 小时前
Qt MaintenanceTool 使用国内镜像加速下载(超详细教程)
开发语言·qt
小小龙学IT19 小时前
Qt 元对象系统(Meta-Object System)深度解析:从 MOC 到反射式编程
数据库·qt
小王C语言20 小时前
QT 基础:QT SDK 下载、配置、新建项目、项目代码解释
开发语言·qt
张人玉1 天前
基于 Python + PyQt5 的水果目标检测与分割可视化系统——水果深度学习识别可视化大屏
python·深度学习·qt·目标检测·sqlite·echarts