【C++进阶】智能指针
1. 为什么需要智能指针?
1.1 传统指针的问题
先看一个典型的内存管理问题:
cpp
#include <iostream>
#include <stdexcept>
int divide() {
int a, b;
std::cin >> a >> b;
if (b == 0)
throw std::invalid_argument("除0错误");
return a / b;
}
void riskyFunction() {
// 问题1:如果new抛出异常会怎样?
int* p1 = new int;
int* p2 = new int;
// 问题2:如果divide抛出异常会怎样?
std::cout << divide() << std::endl;
// 如果上面抛出异常,这里的delete不会执行!
delete p1;
delete p2;
}
int main() {
try {
riskyFunction();
}
catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
return 0;
}
问题分析:
- 如果
new int抛出异常,已分配的内存无法释放 - 如果
divide()抛出异常,delete语句不会执行 - 最终导致内存泄漏
2. 内存泄漏
2.1 什么是内存泄漏
内存泄漏:程序未能释放已经不再使用的内存,导致内存的浪费。
危害:
- 长期运行的程序会逐渐变慢
- 最终可能导致程序崩溃
- 系统资源耗尽
cpp
#include <vector>
void demonstrateMemoryLeaks() {
// 1. 直接忘记释放
int* leak1 = new int(100);
// 2. 异常导致未释放
std::vector<int>* leak2 = new std::vector<int>(1000);
throw std::runtime_error("意外异常");
delete leak2; // 这行不会执行!
// 3. 早期返回忘记释放
int* leak3 = new int[100];
if (/* 某些条件 */) {
return; // 忘记delete[]
}
delete[] leak3;
}
2.2 内存泄漏分类
- 堆内存泄漏 :
new/malloc分配的内存未释放 - 系统资源泄漏:文件描述符、套接字等未关闭
2.3 如何避免内存泄漏
- 编码规范 :匹配的
new/delete,malloc/free - RAII思想:资源在对象构造时获取,析构时释放
- 智能指针:自动管理资源生命周期
3. RAII:资源获取即初始化
RAII是智能指针的核心理念:
cpp
#include <iostream>
template<typename T>
class SimpleSmartPtr {
private:
T* _ptr;
public:
// 构造函数获取资源
explicit SimpleSmartPtr(T* ptr = nullptr) : _ptr(ptr) {
std::cout << "获取资源: " << _ptr << std::endl;
}
// 析构函数释放资源
~SimpleSmartPtr() {
std::cout << "释放资源: " << _ptr << std::endl;
if (_ptr) {
delete _ptr;
}
}
// 禁用拷贝(简单版本)
SimpleSmartPtr(const SimpleSmartPtr&) = delete;
SimpleSmartPtr& operator=(const SimpleSmartPtr&) = delete;
// 指针操作
T& operator*() const { return *_ptr; }
T* operator->() const { return _ptr; }
T* get() const { return _ptr; }
};
void safeFunction() {
SimpleSmartPtr<int> ptr1(new int(42));
SimpleSmartPtr<double> ptr2(new double(3.14));
*ptr1 = 100;
std::cout << "值: " << *ptr1 << std::endl;
// 即使抛出异常,资源也会自动释放
throw std::runtime_error("测试异常");
}
int main() {
try {
safeFunction();
}
catch (const std::exception& e) {
std::cout << "异常: " << e.what() << std::endl;
}
return 0;
}
4. C++智能指针详解
4.1 std::unique_ptr:独占所有权
cpp
#include <iostream>
#include <memory>
void uniquePtrDemo() {
// 创建unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int[]> arrPtr = std::make_unique<int[]>(10);
// 使用指针
*ptr1 = 100;
arrPtr[0] = 1;
arrPtr[1] = 2;
// 获取原始指针
int* rawPtr = ptr1.get();
// 释放所有权
int* releasedPtr = ptr1.release();
delete releasedPtr;
// 重置指针
ptr1.reset(new int(200));
std::cout << "unique_ptr演示完成" << std::endl;
}
// 工厂函数返回unique_ptr
std::unique_ptr<int> createNumber(int value) {
return std::make_unique<int>(value);
}
// 作为参数传递(不转让所有权)
void processNumber(const std::unique_ptr<int>& ptr) {
if (ptr) {
std::cout << "处理数字: " << *ptr << std::endl;
}
}
// 转让所有权
std::unique_ptr<int> transferOwnership(std::unique_ptr<int> ptr) {
if (ptr) {
*ptr += 10;
}
return ptr;
}
int main() {
uniquePtrDemo();
auto num = createNumber(50);
processNumber(num);
auto newNum = transferOwnership(std::move(num));
std::cout << "转移后: " << *newNum << std::endl;
return 0;
}
4.2 std::shared_ptr:共享所有权
cpp
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
struct Data {
int value;
std::string name;
Data(int v, const std::string& n) : value(v), name(n) {
std::cout << "构造Data: " << name << std::endl;
}
~Data() {
std::cout << "析构Data: " << name << std::endl;
}
};
void sharedPtrDemo() {
// 创建shared_ptr
std::shared_ptr<Data> ptr1 = std::make_shared<Data>(100, "第一个对象");
{
// 共享所有权
std::shared_ptr<Data> ptr2 = ptr1;
std::shared_ptr<Data> ptr3 = ptr1;
std::cout << "引用计数: " << ptr1.use_count() << std::endl; // 3
ptr2->value = 200;
std::cout << "通过ptr1访问: " << ptr1->value << std::endl; // 200
}
std::cout << "引用计数: " << ptr1.use_count() << std::endl; // 1
}
// 多线程共享示例
void threadFunction(std::shared_ptr<Data> data, int id) {
std::cout << "线程" << id << "启动,引用计数: " << data.use_count() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
data->value += id;
}
void multiThreadDemo() {
auto sharedData = std::make_shared<Data>(0, "共享数据");
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(threadFunction, sharedData, i + 1);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终值: " << sharedData->value << std::endl;
}
int main() {
sharedPtrDemo();
multiThreadDemo();
return 0;
}
4.3 std::weak_ptr:解决循环引用
cpp
#include <iostream>
#include <memory>
struct Node {
int data;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用
Node(int val) : data(val) {
std::cout << "构造Node: " << data << std::endl;
}
~Node() {
std::cout << "析构Node: " << data << std::endl;
}
};
void weakPtrDemo() {
// 创建两个节点
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
// 建立双向链接
node1->next = node2; // shared_ptr,增加引用计数
node2->prev = node1; // weak_ptr,不增加引用计数
std::cout << "node1引用计数: " << node1.use_count() << std::endl; // 1
std::cout << "node2引用计数: " << node2.use_count() << std::endl; // 2
// 使用weak_ptr访问对象
if (auto locked = node2->prev.lock()) {
std::cout << "通过weak_ptr访问: " << locked->data << std::endl;
}
// 检查weak_ptr是否有效
std::cout << "weak_ptr是否过期: " << node2->prev.expired() << std::endl;
}
// 循环引用问题演示
struct BadNode {
int data;
std::shared_ptr<BadNode> next;
std::shared_ptr<BadNode> prev; // 错误:使用shared_ptr导致循环引用
BadNode(int val) : data(val) {}
};
void demonstrateCircularReference() {
auto node1 = std::make_shared<BadNode>(1);
auto node2 = std::make_shared<BadNode>(2);
node1->next = node2;
node2->prev = node1; // 循环引用!
std::cout << "循环引用示例 - 节点不会被自动释放" << std::endl;
// 离开作用域时,node1和node2的引用计数都为1,无法自动释放
}
int main() {
weakPtrDemo();
demonstrateCircularReference(); // 注意:这里会有内存泄漏!
return 0;
}
4.4 自定义删除器
cpp
#include <iostream>
#include <memory>
#include <cstdio>
// 自定义删除器:函数对象版本
struct FileDeleter {
void operator()(std::FILE* file) const {
if (file) {
std::fclose(file);
std::cout << "文件已关闭" << std::endl;
}
}
};
// 自定义删除器:函数指针版本
void arrayDeleter(int* ptr) {
std::cout << "删除数组: " << ptr << std::endl;
delete[] ptr;
}
void customDeleterDemo() {
// 1. 文件资源管理
std::unique_ptr<std::FILE, FileDeleter> filePtr(
std::fopen("test.txt", "w"),
FileDeleter{}
);
if (filePtr) {
std::fputs("Hello, World!", filePtr.get());
}
// 2. 数组自定义删除
std::shared_ptr<int> arrayPtr(new int[10], arrayDeleter);
// 3. lambda表达式删除器
auto lambdaDeleter = [](int* ptr) {
std::cout << "Lambda删除: " << ptr << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(lambdaDeleter)> customPtr(new int(42), lambdaDeleter);
// 4. malloc/free 管理
std::shared_ptr<void> mallocPtr(std::malloc(100), std::free);
std::cout << "自定义删除器演示完成" << std::endl;
}
int main() {
customDeleterDemo();
return 0;
}
5. 智能指针的线程安全性
cpp
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
#include <mutex>
struct Counter {
int value = 0;
std::mutex mtx;
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++value;
}
};
void threadSafeDemo() {
auto counter = std::make_shared<Counter>();
constexpr int numThreads = 10;
constexpr int incrementsPerThread = 1000;
std::vector<std::thread> threads;
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back([counter]() {
for (int j = 0; j < incrementsPerThread; ++j) {
counter->increment();
}
});
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数: " << counter->value << std::endl;
std::cout << "期望值: " << numThreads * incrementsPerThread << std::endl;
}
// 引用计数的线程安全性
void referenceCountDemo() {
auto sharedData = std::make_shared<int>(0);
std::thread t1([sharedData]() {
auto localCopy = sharedData; // 引用计数安全递增
*localCopy = 100;
});
std::thread t2([sharedData]() {
auto localCopy = sharedData; // 引用计数安全递增
*localCopy = 200;
});
t1.join();
t2.join();
std::cout << "引用计数线程安全演示完成" << std::endl;
}
int main() {
threadSafeDemo();
referenceCountDemo();
return 0;
}
6. 智能指针实践
6.1 使用make_shared和make_unique
cpp
#include <iostream>
#include <memory>
class Resource {
private:
int _id;
std::string _name;
public:
Resource(int id, const std::string& name) : _id(id), _name(name) {
std::cout << "构造Resource: " << _name << std::endl;
}
~Resource() {
std::cout << "析构Resource: " << _name << std::endl;
}
void use() const {
std::cout << "使用Resource: " << _name << " (ID: " << _id << ")" << std::endl;
}
};
void bestPractices() {
// 推荐:使用make_shared/make_unique
auto ptr1 = std::make_shared<Resource>(1, "动态资源");
auto ptr2 = std::make_unique<Resource>(2, "独占资源");
// 不推荐:直接new(可能造成异常不安全)
// std::shared_ptr<Resource> badPtr(new Resource(3, "不安全的资源"));
ptr1->use();
ptr2->use();
// 工厂模式返回智能指针
auto createResource = [](int id, const std::string& name) {
return std::make_shared<Resource>(id, name);
};
auto newResource = createResource(4, "工厂创建的资源");
newResource->use();
}
// 作为函数参数传递的最佳实践
void processResource(std::shared_ptr<Resource> res) { // 值传递:共享所有权
if (res) {
res->use();
}
}
void observeResource(const std::shared_ptr<Resource>& res) { // const引用:仅观察
if (res) {
res->use();
}
}
void modifyResource(std::shared_ptr<Resource>& res) { // 非常量引用:可能修改指针
if (res) {
res->use();
}
}
int main() {
bestPractices();
auto resource = std::make_shared<Resource>(5, "测试资源");
processResource(resource); // 共享所有权
observeResource(resource); // 仅观察
modifyResource(resource); // 可能修改
return 0;
}
6.2 智能指针与STL容器
cpp
#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <string>
void stlContainerDemo() {
// vector中的智能指针
std::vector<std::shared_ptr<int>> numbers;
for (int i = 0; i < 5; ++i) {
numbers.push_back(std::make_shared<int>(i * 10));
}
for (const auto& num : numbers) {
std::cout << *num << " ";
}
std::cout << std::endl;
// map中的智能指针
std::map<std::string, std::shared_ptr<Resource>> resourceMap;
resourceMap["res1"] = std::make_shared<Resource>(1, "资源1");
resourceMap["res2"] = std::make_shared<Resource>(2, "资源2");
for (const auto& [key, value] : resourceMap) {
std::cout << key << ": ";
value->use();
}
// unique_ptr在容器中的使用(需要移动语义)
std::vector<std::unique_ptr<int>> uniqueNumbers;
for (int i = 0; i < 3; ++i) {
uniqueNumbers.push_back(std::make_unique<int>(i * 100));
}
// 移动unique_ptr
auto temp = std::make_unique<int>(999);
uniqueNumbers.push_back(std::move(temp));
}
int main() {
stlContainerDemo();
return 0;
}
7. 总结
7.1 智能指针选择指南
| 场景 | 推荐智能指针 | 理由 |
|---|---|---|
| 独占所有权 | std::unique_ptr |
零开销,性能最好 |
| 共享所有权 | std::shared_ptr |
引用计数,自动管理生命周期 |
| 避免循环引用 | std::weak_ptr |
不增加引用计数 |
| 数组管理 | std::unique_ptr<T[]> |
支持数组语法 |
| 多线程共享 | std::shared_ptr |
引用计数线程安全 |
7.2 关键
- 优先使用
make_shared和make_unique - 使用
std::unique_ptr作为默认选择 - 需要共享所有权时才使用
std::shared_ptr - 循环引用时使用
std::weak_ptr - 明确函数参数的所有权语义
7.3 性能考虑
std::unique_ptr:零运行时开销std::shared_ptr:引用计数有少量开销make_shared可以合并内存分配,提高性能