C++--unique_ptr

377 阅读5分钟

在 C++ 中,std::unique_ptr 是 C++ 标准库 <memory> 头文件中提供的一种智能指针。它遵循独占所有权语义,即同一时间内,只能有一个 std::unique_ptr 指向某个对象。当这个 std::unique_ptr 被销毁时,它所指向的对象也会被自动销毁,这有助于避免内存泄漏。

作用

  • 自动内存管理std::unique_ptr 可以在其生命周期结束时自动释放所管理的对象的内存,无需手动调用 delete,从而减少了因忘记释放内存而导致的内存泄漏风险。
  • 明确所有权:通过独占所有权的特性,使得代码中对象的所有权更加清晰,避免多个指针同时管理同一个对象可能带来的问题,如多次释放内存。

场景

  • 管理动态分配的资源:当需要动态分配内存时,使用 std::unique_ptr 可以确保在资源不再使用时自动释放。例如,在一个函数中创建一个动态分配的对象,使用 std::unique_ptr 管理该对象,即使函数提前返回或抛出异常,对象的内存也会被正确释放。
  • 作为类的成员变量:当类需要拥有一个动态分配的对象时,使用 std::unique_ptr 作为成员变量可以简化类的析构函数,因为不需要手动释放成员对象的内存。
  • 在容器中存储动态分配的对象:可以将 std::unique_ptr 存储在标准容器(如 std::vector)中,实现对动态分配对象的高效管理。

用法举例

1. 基本使用

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

int main() {
    // 创建一个 std::unique_ptr 并指向一个新的 MyClass 对象
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    // 调用对象的成员函数
    ptr->doSomething();
    // std::unique_ptr 离开作用域,自动释放所管理的对象
    return 0;
}

在这个例子中,std::make_unique<MyClass>() 用于创建一个 MyClass 对象,并返回一个 std::unique_ptr 指向该对象。当 ptr 离开 main 函数的作用域时,std::unique_ptr 的析构函数会被调用,从而自动释放所管理的 MyClass 对象的内存。

2. 作为函数参数和返回值

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

// 接受一个 std::unique_ptr 作为参数
void processObject(std::unique_ptr<MyClass> obj) {
    obj->doSomething();
}

// 返回一个 std::unique_ptr
std::unique_ptr<MyClass> createObject() {
    return std::make_unique<MyClass>();
}

int main() {
    std::unique_ptr<MyClass> obj = createObject();
    processObject(std::move(obj));
    // obj 现在为空,因为所有权已经转移
    if (!obj) {
        std::cout << "obj is empty" << std::endl;
    }
    return 0;
}

在这个例子中,createObject 函数返回一个 std::unique_ptr,表示创建并返回一个新的 MyClass 对象的所有权。processObject 函数接受一个 std::unique_ptr 作为参数,当调用 processObject(std::move(obj)) 时,使用 std::move 将 obj 的所有权转移给 processObject 函数的参数,之后 obj 变为空指针。

3. 容器中使用

#include <iostream>
#include <memory>
#include <vector>

class MyClass {
public:
    MyClass(int value) : data(value) { std::cout << "MyClass constructor with value: " << data << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor with value: " << data << std::endl; }
    int getData() const { return data; }
private:
    int data;
};

int main() {
    std::vector<std::unique_ptr<MyClass>> vec;
    // 向容器中添加元素
    vec.push_back(std::make_unique<MyClass>(1));
    vec.push_back(std::make_unique<MyClass>(2));
    // 访问容器中的元素
    for (const auto& ptr : vec) {
        std::cout << "Data: " << ptr->getData() << std::endl;
    }
    // 容器销毁时,自动释放所有元素的内存
    return 0;
}

在这个例子中,std::vector 存储了多个 std::unique_ptr<MyClass> 对象。当向容器中添加元素时,使用 std::make_unique 创建新的 MyClass 对象并将其所有权转移给 std::unique_ptr,然后添加到容器中。当容器销毁时,所有 std::unique_ptr 的析构函数会被调用,从而自动释放所管理的 MyClass 对象的内存。

4. 注意

4.1 独占所有权特性
  • 唯一性要求

    • std::unique_ptr 遵循独占所有权语义,同一时间只能有一个 std::unique_ptr 指向某个对象。这意味着不能直接将一个 std::unique_ptr 赋值给另一个 std::unique_ptr,因为这会破坏独占性。
  • 所有权转移

    • 若要转移 std::unique_ptr 的所有权,需使用 std::move 函数。调用 std::move 后,原 std::unique_ptr 会失去对对象的所有权,变为空指针。
4.2 避免与原始指针混用
  • 手动删除风险

    • 若将 std::unique_ptr 与手动管理的原始指针混用,可能会导致对象被多次删除,从而引发未定义行为。
  • 示例代码

#include <memory>

class MyClass {};

int main() {
    MyClass* rawPtr = new MyClass();
    std::unique_ptr<MyClass> uniquePtr(rawPtr);
    // 错误,手动删除已被 std::unique_ptr 管理的对象
    // delete rawPtr; 
    return 0;
}
  • 推荐创建方式

    • 建议使用 std::make_unique 来创建 std::unique_ptr,这样可以避免手动使用 new 带来的潜在问题,并且代码更加简洁和安全。
  • 示例代码

#include <memory>

class MyClass {};

int main() {
    std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();
    return 0;
}
4.3 自定义删除器
  • 使用场景
    • 当需要管理非传统的资源(如文件句柄、数据库连接等)时,可使用自定义删除器。自定义删除器可以是函数指针、函数对象或 Lambda 表达式。
    • 示例代码
#include <memory>
#include <cstdio>

class FileDeleter {
public:
    void operator()(FILE* file) {
        if (file) {
            std::fclose(file);
        }
    }
};

int main() {
    FILE* file = std::fopen("test.txt", "w");
    std::unique_ptr<FILE, FileDeleter> filePtr(file);
    return 0;
}
  • 删除器类型影响
    • 自定义删除器会影响 std::unique_ptr 的类型。在使用自定义删除器时,需要在 std::unique_ptr 的模板参数中指定删除器的类型。
4.4 空指针检查
  • 访问空指针风险

    • 在使用 std::unique_ptr 之前,应检查其是否为空,避免对空指针进行解引用操作,否则会导致未定义行为。
  • 示例代码

#include <memory>

class MyClass {
public:
    void doSomething() {}
};

int main() {
    std::unique_ptr<MyClass> ptr;
    if (ptr) {
        ptr->doSomething();
    }
    return 0;
}
4.5 异常安全
  • 资源管理优势
    • std::unique_ptr 能够在异常发生时自动释放所管理的对象,从而保证资源的正确释放,避免资源泄漏。

  • 示例代码
#include <memory>
#include <stdexcept>

class MyClass {};

void someFunction() {
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    // 可能会抛出异常的代码
    if (true) {
        throw std::runtime_error("An error occurred");
    }
    // 即使抛出异常,ptr 所管理的对象也会被自动释放
}

int main() {
    try {
        someFunction();
    } catch (const std::exception& e) {
        // 处理异常
    }
    return 0;
}