C++中如何正确使用std::unique_ptr实现资源管理?

在C++17之前,手动管理动态分配的内存是一项常见的错误来源,导致内存泄漏、悬空指针等问题。std::unique_ptr 是一种智能指针,能够自动管理资源的生命周期,确保在作用域结束时自动释放。下面从使用场景、构造方式、转移所有权、与自定义删除器、以及在容器中的使用等方面,系统讲解如何正确使用 std::unique_ptr


1. 基本使用

#include <memory>
#include <iostream>

struct Resource {
    Resource()  { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

int main() {
    std::unique_ptr <Resource> ptr = std::make_unique<Resource>();
    // 资源自动释放
}
  • make_unique 是推荐的构造方式,避免手动 new
  • unique_ptr 只能拥有单一指针,复制被禁止,转移通过 std::move

2. 转移所有权

std::unique_ptr <Resource> foo() {
    return std::make_unique <Resource>();   // NRVO 或移动语义
}

std::unique_ptr <Resource> bar() {
    std::unique_ptr <Resource> ptr = std::make_unique<Resource>();
    return ptr;    // 移动返回
}
  • 通过 std::move 可以显式转移:
std::unique_ptr <Resource> a = std::make_unique<Resource>();
std::unique_ptr <Resource> b = std::move(a);
  • 转移后 a 变为 nullptr,不再拥有资源。

3. 自定义删除器

在默认情况下 unique_ptrdelete 释放对象。若需要特殊释放逻辑,例如关闭文件句柄或网络连接,可自定义删除器:

struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) fclose(fp);
    }
};

std::unique_ptr<FILE, FileCloser> filePtr(fopen("log.txt", "w"));
  • 自定义删除器可以是函数指针、函数对象、或 lambda。
auto deleter = [](int* p){ delete[] p; };
std::unique_ptr<int[], decltype(deleter)> arr(new int[10], deleter);

4. 与数组配合

std::unique_ptr<int[]> arr(new int[10]); // 注意使用[]
int val = arr[3];
  • 对于数组,不能使用 delete,需要 delete[],智能指针通过模板参数 int[] 自动处理。

5. 与标准容器配合

unique_ptr 可以存放在 std::vectorstd::list 等容器中:

std::vector<std::unique_ptr<Resource>> vec;
vec.push_back(std::make_unique <Resource>());
  • 由于 unique_ptr 不可复制,容器只能移动元素。
  • 删除元素时,容器会自动销毁对应的 unique_ptr,进而释放资源。

6. 与共享所有权的区别

  • std::unique_ptr:单一所有权,转移后原指针失效。适合局部资源或父子关系。
  • std::shared_ptr:共享所有权,引用计数。适合跨线程共享或多对象引用。

7. 小技巧与注意事项

  1. 避免裸 new:始终使用 std::make_uniquestd::make_shared
  2. 不需要 delete:手动释放会导致二次释放错误。
  3. 保持 nullptr:在转移后及时检查 ptr == nullptr
  4. 自定义删除器要匹配分配方式:例如 new[] 必须用 delete[]
  5. 不要在 unique_ptr 中存放裸指针:如 `std::unique_ptr ` 里存 `int*`,若外部同时持有指针可能导致双重删除。

8. 典型应用示例

8.1 资源池

class Connection {
public:
    Connection() { /* 连接初始化 */ }
    ~Connection() { /* 关闭连接 */ }
};

class ConnectionPool {
    std::vector<std::unique_ptr<Connection>> pool;
public:
    std::unique_ptr <Connection> acquire() {
        if (pool.empty()) return std::make_unique <Connection>();
        auto conn = std::move(pool.back());
        pool.pop_back();
        return conn;
    }
    void release(std::unique_ptr <Connection> conn) {
        pool.push_back(std::move(conn));
    }
};

8.2 工厂函数

std::unique_ptr <Animal> createAnimal(const std::string& type) {
    if (type == "cat") return std::make_unique <Cat>();
    if (type == "dog") return std::make_unique <Dog>();
    return nullptr;
}

工厂返回 unique_ptr,调用者立即获得资源所有权,避免忘记释放。


9. 结语

std::unique_ptr 是现代 C++ 资源管理的核心工具,正确使用它可以极大降低内存泄漏风险,提高代码安全性与可读性。记住:

  • make_uniquemake_shared
  • 通过 std::move 转移所有权。
  • 关注自定义删除器与数组的特殊处理。
  • 与标准容器配合时利用移动语义。

在日常项目中,养成使用 unique_ptr 的习惯,几乎可以消除手工内存管理的烦恼。祝编码愉快!

发表评论