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. 常见应用场景
- RAII(资源获取即初始化):使用
unique_ptr
来管理资源(如动态内存、文件句柄、锁等),确保资源在不再使用时自动释放。 - 动态分配的对象管理:避免手动
new
和delete
引发的内存泄漏问题。 - 实现工厂模式:工厂模式中返回对象的所有权可以通过
unique_ptr
传递,以确保对象生命周期的安全管理。 - 函数返回动态对象:函数返回动态分配的对象时,可以返回
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_ptr
与 unique_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
,创建的每个产品在离开作用域时自动释放,减少内存管理的复杂性。