C++ 智能指针 unique_ptr

1. unique_ptr 基本概念

std::unique_ptr 是 C++ 标准库中一种智能指针,用于确保对象拥有唯一的所有权。也就是说,某一时刻,只有一个 unique_ptr 能管理特定对象的生命周期,当 unique_ptr 被销毁时,所指向的对象也随之销毁。它主要用于解决手动管理动态内存带来的问题,防止内存泄漏和未定义行为。

特点:
  • 独占所有权:对象的生命周期只能被一个 unique_ptr 管理,无法进行复制(复制会导致编译错误)。
  • 自动析构:当 unique_ptr 离开作用域或者被重置时,它会自动释放所管理的对象。
  • 轻量:与裸指针相比,unique_ptr 只额外持有一个指向所管理对象的指针,开销极小。

2. 基本用法

#include <iostream>
#include <memory>

class Test {
public:
    Test() { std::cout << "Test created\n"; }
    ~Test() { std::cout << "Test destroyed\n"; }
    void show() { std::cout << "Test::show\n"; }
};

int main() {
    std::unique_ptr<Test> ptr1(new Test());  // 创建 unique_ptr 管理 Test 对象
    ptr1->show();  // 访问 Test 对象的方法
    // Test 对象将在 ptr1 作用域结束时自动销毁
}
输出:

Test created
Test::show
Test destroyed

如上所示:初始化方法共两种:

std::unique_ptr<Test> ptr1(new Test());

std::unique_ptr<Test> ptr2 = std::make_unique<Test>();

在上面的例子中,当 ptr1 离开作用域时,Test 对象会自动被销毁。

3. 转移所有权

unique_ptr 不允许复制,但可以通过 std::move 将所有权转移。

#include <iostream>
#include <memory>

class Test {
public:
    Test() { std::cout << "Test created\n"; }
    ~Test() { std::cout << "Test destroyed\n"; }
    void show() { std::cout << "Test::show\n"; }
};

int main() {
    std::unique_ptr<Test> ptr1(new Test());
    std::unique_ptr<Test> ptr2 = std::make_unique<Test>();

    // 转移所有权
    ptr2 = std::move(ptr1);  // 现在 ptr1 不再拥有 Test 对象
    if (!ptr1) {
        std::cout << "ptr1 is null\n";
    }
    ptr2->show();
}

输出:

Test created
Test created
Test destroyed
ptr1 is null
Test::show
Test destroyed

4. 自定义删除器

unique_ptr 支持自定义删除器,以处理非默认的对象销毁行为。例如,管理通过 fopen 打开的文件:

#include <cstdio>
#include <memory>

int main() {
    std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("test.txt", "w"), &fclose);
    if (filePtr) {
        std::fprintf(filePtr.get(), "Hello, World!");
    }
}

自定义删除器在定义 unique_ptr 时需要指定删除函数的类型。

5. 常见应用场景

  1. RAII(资源获取即初始化):使用 unique_ptr 来管理资源(如动态内存、文件句柄、锁等),确保资源在不再使用时自动释放。
  2. 动态分配的对象管理:避免手动 newdelete 引发的内存泄漏问题。
  3. 实现工厂模式:工厂模式中返回对象的所有权可以通过 unique_ptr 传递,以确保对象生命周期的安全管理。
  4. 函数返回动态对象:函数返回动态分配的对象时,可以返回 unique_ptr,确保调用者获取对象所有权,并避免内存泄漏。

6. 注意事项和常见坑

6.1 禁止复制

unique_ptr 无法复制,如果尝试这样做,编译器会报错。

std::unique_ptr<Test> ptr1(new Test());
std::unique_ptr<Test> ptr2 = ptr1;  // 错误:unique_ptr 不允许复制

6.2 不能使用数组类型的 new[]

unique_ptr 需要使用 std::unique_ptr<T[]> 进行数组管理,如果直接用 std::unique_ptr<T> 处理数组,会导致内存释放错误。

std::unique_ptr<int[]> arr(new int[10]);  // 正确:用 unique_ptr 管理动态数组

6.3 必须小心自定义删除器的类型

在使用自定义删除器时,必须确保删除器的类型与 unique_ptr 定义时一致,否则可能会导致删除行为不正确,甚至内存泄漏。

6.4 避免不必要的内存分配

unique_ptr 适用于管理堆内存对象,但如果对象不需要动态分配,则使用局部对象更高效。

std::unique_ptr<int> ptr(new int(5));  // 动态分配
int localVar = 5;  // 更高效

6.5 小心 std::move 后的空指针访问

在将 unique_ptr 传递或转移所有权后,需要检查指针是否为空,避免解引用空指针。

std::unique_ptr<Test> ptr1(new Test());
std::unique_ptr<Test> ptr2 = std::move(ptr1);
ptr1->show();  // 错误:ptr1 为空,不能再使用

6.6 避免循环引用

虽然 unique_ptr 本身不支持共享所有权,但如果与 std::shared_ptr 混用时,必须小心避免循环引用,防止内存泄漏。比如 shared_ptrunique_ptr 之间相互指向时,需要特别注意删除器和生命周期的管理。

7. 工厂模式

工厂模式是一种创建型设计模式,用于在代码中提供一种接口来创建对象,而无需指定它们的具体类。其核心思想是将对象的创建和使用解耦,允许子类决定要实例化的具体类。工厂模式可以分为简单工厂工厂方法抽象工厂三种不同的变体。

在工厂模式中,std::unique_ptr 是非常常见的选择,因为工厂创建的对象通常是在堆上分配的,而 unique_ptr 能确保这些对象在不再需要时被自动销毁,避免内存泄漏。下面我们详细讲解工厂模式中的 unique_ptr 使用点。

1. 简单工厂模式

简单工厂模式中,工厂类通过一个静态方法根据传入的参数创建并返回对象。工厂的使用者不需要关心具体的类细节,只需通过工厂获取对象。

在简单工厂模式中使用 std::unique_ptr 可以确保工厂创建的对象不需要手动管理内存,自动销毁。

示例:

#include <iostream>
#include <memory>

// 产品基类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() = default;
};

// 具体产品A
class ProductA : public Product {
public:
    void use() override {
        std::cout << "Using Product A" << std::endl;
    }
};

// 具体产品B
class ProductB : public Product {
public:
    void use() override {
        std::cout << "Using Product B" << std::endl;
    }
};

// 工厂类
class SimpleFactory {
public:
    // 工厂方法:返回指向Product的unique_ptr
    static std::unique_ptr<Product> createProduct(const std::string &type) {
        if (type == "A") {
            return std::make_unique<ProductA>();  // 创建并返回ProductA
        }
        else if (type == "B") {
            return std::make_unique<ProductB>();  // 创建并返回ProductB
        }
        else {
            return nullptr;  // 未知类型,返回空指针
        }
    }
};

int main() {
    // 使用工厂创建 Product A
    std::unique_ptr<Product> productA = SimpleFactory::createProduct("A");
    productA->use();

    // 使用工厂创建 Product B
    std::unique_ptr<Product> productB = SimpleFactory::createProduct("B");
    productB->use();

    return 0;
}

使用 unique_ptr 的好处:

  • 自动内存管理:工厂创建的对象无需手动释放,unique_ptr 会在其离开作用域时自动释放内存,防止内存泄漏。
  • 独占所有权unique_ptr 确保了工厂创建的对象只有调用者能使用,防止了多指针共享同一资源可能带来的问题。
2. 工厂方法模式

工厂方法模式是一种允许子类决定创建哪个类实例的设计模式。工厂方法提供一个接口来创建对象,而将具体的对象创建推迟到子类。

在这种模式中,每个具体的工厂会返回一个具体类的 std::unique_ptr,由调用者负责接管所有权。

示例:

#include <iostream>
#include <memory>

// 产品基类
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() = default;
};

// 具体产品A
class ProductA : public Product {
public:
    void use() override {
        std::cout << "Using Product A" << std::endl;
    }
};

// 具体产品B
class ProductB : public Product {
public:
    void use() override {
        std::cout << "Using Product B" << std::endl;
    }
};

// 工厂基类
class Factory {
public:
    virtual std::unique_ptr<Product> createProduct() = 0;  // 工厂方法,返回unique_ptr
    virtual ~Factory() = default;
};

// 具体工厂A,负责创建ProductA
class FactoryA : public Factory {
public:
    std::unique_ptr<Product> createProduct() override {
        return std::make_unique<ProductA>();  // 创建并返回ProductA
    }
};

// 具体工厂B,负责创建ProductB
class FactoryB : public Factory {
public:
    std::unique_ptr<Product> createProduct() override {
        return std::make_unique<ProductB>();  // 创建并返回ProductB
    }
};

int main() {
    std::unique_ptr<Factory> factoryA = std::make_unique<FactoryA>();  // 创建工厂A
    std::unique_ptr<Product> productA = factoryA->createProduct();  // 创建ProductA
    productA->use();

    std::unique_ptr<Factory> factoryB = std::make_unique<FactoryB>();  // 创建工厂B
    std::unique_ptr<Product> productB = factoryB->createProduct();  // 创建ProductB
    productB->use();

    return 0;
}

使用 unique_ptr 的好处:

  • 灵活性和扩展性:子类可以方便地扩展,添加新的工厂来创建不同的产品,而不需要更改现有的代码。
  • 内存管理安全:与简单工厂模式相同,unique_ptr 在创建产品时可以确保对象在使用结束后自动销毁。
3. 抽象工厂模式

抽象工厂模式提供了一组相关或依赖对象的创建接口,而无需指定具体的类。它通常用于创建一系列产品,这些产品可以由不同的工厂类生成。

unique_ptr 在抽象工厂模式中也发挥了重要作用,确保复杂对象树中的内存安全和自动释放。

示例:

#include <iostream>
#include <memory>

// 产品基类
class ProductA {
public:
    virtual void useA() = 0;
    virtual ~ProductA() = default;
};

class ProductB {
public:
    virtual void useB() = 0;
    virtual ~ProductB() = default;
};

// 具体产品A1和A2
class ConcreteProductA1 : public ProductA {
public:
    void useA() override {
        std::cout << "Using Product A1" << std::endl;
    }
};

class ConcreteProductA2 : public ProductA {
public:
    void useA() override {
        std::cout << "Using Product A2" << std::endl;
    }
};

// 具体产品B1和B2
class ConcreteProductB1 : public ProductB {
public:
    void useB() override {
        std::cout << "Using Product B1" << std::endl;
    }
};

class ConcreteProductB2 : public ProductB {
public:
    void useB() override {
        std::cout << "Using Product B2" << std::endl;
    }
};

// 抽象工厂基类
class AbstractFactory {
public:
    virtual std::unique_ptr<ProductA> createProductA() = 0;
    virtual std::unique_ptr<ProductB> createProductB() = 0;
    virtual ~AbstractFactory() = default;
};

// 具体工厂1,创建A1和B1
class ConcreteFactory1 : public AbstractFactory {
public:
    std::unique_ptr<ProductA> createProductA() override {
        return std::make_unique<ConcreteProductA1>();  // 创建ProductA1
    }

    std::unique_ptr<ProductB> createProductB() override {
        return std::make_unique<ConcreteProductB1>();  // 创建ProductB1
    }
};

// 具体工厂2,创建A2和B2
class ConcreteFactory2 : public AbstractFactory {
public:
    std::unique_ptr<ProductA> createProductA() override {
        return std::make_unique<ConcreteProductA2>();  // 创建ProductA2
    }

    std::unique_ptr<ProductB> createProductB() override {
        return std::make_unique<ConcreteProductB2>();  // 创建ProductB2
    }
};

int main() {
    // 使用具体工厂1创建产品
    std::unique_ptr<AbstractFactory> factory1 = std::make_unique<ConcreteFactory1>();
    std::unique_ptr<ProductA> productA1 = factory1->createProductA();
    std::unique_ptr<ProductB> productB1 = factory1->createProductB();
    productA1->useA();
    productB1->useB();

    // 使用具体工厂2创建产品
    std::unique_ptr<AbstractFactory> factory2 = std::make_unique<ConcreteFactory2>();
    std::unique_ptr<ProductA> productA2 = factory2->createProductA();
    std::unique_ptr<ProductB> productB2 = factory2->createProductB();
    productA2->useA();
    productB2->useB();

    return 0;
}

使用 unique_ptr 的好处:

  • 抽象产品组管理:抽象工厂模式中,工厂创建一组相关对象,unique_ptr 确保创建的每个对象都有明确的生命周期管理。
  • 内存管理一致性:通过 unique_ptr,创建的每个产品在离开作用域时自动释放,减少内存管理的复杂性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值