
之前使用智能指针一直是一知半解,现在有时间了,尝试从源码的角度来理解它。
一、unique_ptr 概述
std::unique_ptr 是 C++11 引入的智能指针,用于管理动态分配的内存资源,其核心特性是独占所有权——同一时间只能有一个 unique_ptr 指向特定对象。当 unique_ptr 离开作用域或被销毁时,它所管理的对象会自动释放,从而有效避免内存泄漏。
核心优势
- 自动资源管理:无需手动调用 delete,降低内存泄漏风险
- 异常安全:即使在异常抛出的情况下,仍能保证资源正确释放
- 移动语义支持:可通过移动操作转移所有权,避免浅拷贝问题
- 自定义删除器:支持非默认的资源释放逻辑(如文件句柄、网络连接)
二、unique_ptr 原理深度解析
2.1 独占所有权模型
unique_ptr 的核心设计思想是独占性,这意味着:
- 不允许拷贝构造和拷贝赋值(通过 = delete显式禁用)
- 仅允许移动构造和移动赋值(通过右值引用实现所有权转移)
- 当 unique_ptr被销毁时,其管理的对象也会被自动删除
2.2 简化版 unique_ptr 实现
下面通过实现一个简化版的 unique_ptr 来理解其工作原理:
#include <utility> // 用于 std::move
// 默认删除器
template <typename T>
struct DefaultDeleter {
    void operator()(T* ptr) const {
        delete ptr; // 对单对象使用 delete
    }
};
// 数组特化版本的删除器
template <typename T>
struct DefaultDeleter<T[]> {
    void operator()(T* ptr) const {
        delete[] ptr; // 对数组使用 delete[]
    }
};
// 简化版 unique_ptr 实现
template <typename T, typename Deleter = DefaultDeleter<T>>
class UniquePtr {
private:
    T* ptr_;          // 管理的原始指针
    Deleter deleter_; // 删除器对象
public:
    // 构造函数:接受原始指针
    explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}
    // 析构函数:释放资源
    ~UniquePtr() {
        if (ptr_) {
            deleter_(ptr_); // 调用删除器释放资源
        }
    }
    // 禁用拷贝构造函数
    UniquePtr(const UniquePtr&) = delete;
    // 禁用拷贝赋值运算符
    UniquePtr& operator=(const UniquePtr&) = delete;
    // 移动构造函数:转移所有权
    UniquePtr(UniquePtr&& other) noexcept 
        : ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
        other.ptr_ = nullptr; // 源指针置空,避免二次释放
    }
    // 移动赋值运算符:转移所有权
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            if (ptr_) {
                deleter_(ptr_); // 释放当前资源
            }
            ptr_ = other.ptr_;          // 转移指针
            deleter_ = std::move(other.deleter_); // 转移删除器
            other.ptr_ = nullptr;       // 源指针置空
        }
        return *this;
    }
    // 解引用运算符
    T& operator*() const {
        return *ptr_;
    }
    // 成员访问运算符
    T* operator->() const {
        return ptr_;
    }
    // 数组访问运算符(特化版本中实现)
    T& operator[](size_t index) const;
    // 获取原始指针
    T* get() const {
        return ptr_;
    }
    // 释放所有权
    T* release() {
        T* temp = ptr_;
        ptr_ = nullptr;
        return temp;
    }
    // 重置指针
    void reset(T* new_ptr = nullptr) {
        if (ptr_) {
            deleter_(ptr_); // 释放当前资源
        }
        ptr_ = new_ptr; // 指向新资源
    }
    // 交换管理的资源
    void swap(UniquePtr& other) noexcept {
        std::swap(ptr_, other.ptr_);
        std::swap(deleter_, other.deleter_);
    }
    // 检查是否管理资源
    explicit operator bool() const {
        return ptr_ != nullptr;
    }
};
// 数组版本的 operator[] 实现
template <typename T, typename Deleter>
class UniquePtr<T[], Deleter> {
    // 实现与上述类似,但增加数组访问运算符
public:
    T& operator[](size_t index) const {
        return ptr_[index];
    }
    // 其他成员函数与单对象版本类似...
};
2.3 关键技术点解析
- 
删除器设计 - 默认使用 DefaultDeleter,对单对象使用delete,对数组使用delete[]
- 支持自定义删除器,满足特殊资源释放需求(如文件、网络连接)
 
- 默认使用 
- 
移动语义实现 - 通过右值引用(&&)实现移动构造和移动赋值
- 转移所有权后将源指针置空,确保资源唯一管理
 
- 通过右值引用(
- 
禁止拷贝 - 通过 = delete显式禁用拷贝构造和拷贝赋值
- 确保同一时间只有一个智能指针管理资源
 
- 通过 
三、unique_ptr 使用详解
3.1 基本用法
#include <memory>
#include <iostream>
struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
    void do_something() { std::cout << "Doing something\n"; }
};
int main() {
    // 创建 unique_ptr,管理动态分配的对象
    std::unique_ptr<MyClass> ptr1(new MyClass());
    
    // 使用 make_unique 创建(C++14 引入,更安全)
    auto ptr2 = std::make_unique<MyClass>();
    
    // 访问成员函数
    ptr1->do_something();
    (*ptr2).do_something();
    
    // 检查是否为空
    if (ptr1) {
        std::cout << "ptr1 is not null\n";
    }
    
    // 转移所有权
    std::unique_ptr<MyClass> ptr3 = std::move(ptr1);
    if (!ptr1) { // ptr1 现在为空
        std::cout << "ptr1 is null after move\n";
    }
    
    // 释放资源
    ptr3.reset(); // 显式释放,此时会调用析构函数
    if (!ptr3) {
        std::cout << "ptr3 is null after reset\n";
    }
    
    return 0;
}
3.2 数组管理
unique_ptr 对数组有专门的特化版本,支持 operator[] 访问:
// 管理动态数组
auto arr_ptr = std::make_unique<int[]>(5); // 创建包含5个int的数组
// 访问数组元素
for (int i = 0; i < 5; ++i) {
    arr_ptr[i] = i * 10;
    std::cout << arr_ptr[i] << " ";
}
// 输出:0 10 20 30 40 
// 数组版本会自动使用 delete[] 释放资源
3.3 自定义删除器
当管理非内存资源(如文件句柄、网络套接字)时,可使用自定义删除器:
#include <cstdio>
// 自定义文件删除器
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) {
            std::fclose(fp);
            std::cout << "File closed\n";
        }
    }
};
int main() {
    // 使用自定义删除器的 unique_ptr
    std::unique_ptr<FILE, FileDeleter> file_ptr(
        std::fopen("example.txt", "w"), FileDeleter()
    );
    
    if (file_ptr) {
        std::fputs("Hello, unique_ptr!", file_ptr.get());
    }
    // 离开作用域时自动调用 FileDeleter 关闭文件
    
    return 0;
}
也可使用 lambda 表达式作为删除器:
auto ptr = std::unique_ptr<MyClass, void(*)(MyClass*)>(
    new MyClass(), 
    [](MyClass* p) {
        std::cout << "Custom deleter called\n";
        delete p;
    }
);
3.4 与多态结合使用
unique_ptr 支持基类指针指向派生类对象,实现多态:
struct Base {
    virtual void foo() { std::cout << "Base::foo\n"; }
    virtual ~Base() = default; // 基类析构函数必须为虚函数
};
struct Derived : Base {
    void foo() override { std::cout << "Derived::foo\n"; }
};
int main() {
    std::unique_ptr<Base> base_ptr = std::make_unique<Derived>();
    base_ptr->foo(); // 多态调用,输出 "Derived::foo"
    
    return 0;
}
注意:基类析构函数必须为虚函数,否则会导致未定义行为。
四、高级应用场景
4.1 作为函数返回值
unique_ptr 可作为函数返回值,自动转移所有权:
std::unique_ptr<MyClass> create_object() {
    return std::make_unique<MyClass>(); // 自动移动返回
}
int main() {
    auto obj = create_object(); // 接收返回的 unique_ptr
    obj->do_something();
    return 0;
}
4.2 在容器中使用
unique_ptr 可存储在支持移动语义的容器中(如 std::vector):
#include <vector>
int main() {
    std::vector<std::unique_ptr<MyClass>> objects;
    
    // 添加元素(需要使用 std::move)
    objects.push_back(std::make_unique<MyClass>());
    objects.emplace_back(new MyClass()); // 更高效
    
    // 遍历容器
    for (const auto& ptr : objects) {
        ptr->do_something();
    }
    
    return 0;
}
4.3 实现 PImpl 惯用法
unique_ptr 非常适合实现 PImpl(Pointer to Implementation)惯用法,隐藏实现细节:
// 头文件 MyClass.h
class MyClass {
private:
    // 前向声明实现类
    class Impl;
    std::unique_ptr<Impl> pimpl; // 指向实现的 unique_ptr
public:
    MyClass();
    ~MyClass(); // 需要在 cpp 文件中定义,因为 Impl 在此处不完整
    void do_something();
};
// 实现文件 MyClass.cpp
class MyClass::Impl {
public:
    void do_something() {
        // 实际实现
    }
};
MyClass::MyClass() : pimpl(std::make_unique<Impl>()) {}
MyClass::~MyClass() = default; // 此时 Impl 已完整定义
void MyClass::do_something() { pimpl->do_something(); }
五、注意事项与最佳实践
5.1 避免的操作
- 
不要将原始指针交给多个 unique_ptr // 错误示例:多个 unique_ptr 管理同一资源 int* raw_ptr = new int(10); std::unique_ptr<int> ptr1(raw_ptr); std::unique_ptr<int> ptr2(raw_ptr); // 严重错误!双重释放
- 
不要将 unique_ptr 管理的指针手动释放 // 错误示例:手动释放导致二次释放 auto ptr = std::make_unique<int>(10); delete ptr.get(); // 错误!unique_ptr 析构时会再次释放
- 
避免在 C 风格函数中使用 unique_ptr // 错误示例:C 函数不知道 unique_ptr 的存在 void c_function(int* p) { /* ... */ } auto ptr = std::make_unique<int>(10); c_function(ptr.release()); // 正确:释放所有权 // c_function(ptr.get()); // 危险:如果函数存储了指针会导致问题
5.2 最佳实践
- 
优先使用 std::make_unique - 更安全:避免资源泄漏(如在构造函数抛出异常时)
- 更高效:减少一次指针拷贝
- 语法更简洁
 auto ptr1 = std::make_unique<MyClass>(); // 推荐 auto ptr2 = std::unique_ptr<MyClass>(new MyClass()); // 不推荐
- 
使用移动语义转移所有权 auto ptr1 = std::make_unique<MyClass>(); auto ptr2 = std::move(ptr1); // 正确:转移所有权 // auto ptr3 = ptr2; // 错误:禁止拷贝
- 
正确处理数组 - 使用 unique_ptr<T[]>特化版本
- 避免使用 unique_ptr<T>管理数组
 
- 使用 
- 
自定义删除器注意事项 - 当使用函数指针作为删除器时,unique_ptr体积会增大
- 优先使用函数对象或 lambda 作为删除器
 
- 当使用函数指针作为删除器时,
六、总结
std::unique_ptr 是 C++ 中管理独占资源的首选智能指针,通过独占所有权和移动语义,在保证性能的同时有效避免内存泄漏。其核心特点包括:
- 独占性:同一时间只有一个 unique_ptr管理资源
- 轻量级:大小与原始指针相同,无额外性能开销
- 安全性:自动释放资源,提供异常安全保证
- 灵活性:支持自定义删除器,适应不同资源管理需求
在实际开发中,应优先考虑使用 unique_ptr 而非原始指针,只有在需要共享所有权时才考虑 std::shared_ptr。
 
 
                     
            
        













 
                    

 
                 
                    