【C++进阶】智能指针

【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 如何避免内存泄漏

  1. 编码规范 :匹配的new/deletemalloc/free
  2. RAII思想:资源在对象构造时获取,析构时释放
  3. 智能指针:自动管理资源生命周期

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 关键

  1. 优先使用make_sharedmake_unique
  2. 使用std::unique_ptr作为默认选择
  3. 需要共享所有权时才使用std::shared_ptr
  4. 循环引用时使用std::weak_ptr
  5. 明确函数参数的所有权语义

7.3 性能考虑

  • std::unique_ptr:零运行时开销
  • std::shared_ptr:引用计数有少量开销
  • make_shared可以合并内存分配,提高性能
相关推荐
怕什么真理无穷2 小时前
C++_面试题_21_字符串操作
java·开发语言·c++
Dream it possible!3 小时前
LeetCode 面试经典 150_二叉树_二叉树展开为链表(74_114_C++_中等)
c++·leetcode·链表·面试·二叉树
yi碗汤园3 小时前
【一文了解】C#反射
开发语言·unity·c#
小羊失眠啦.3 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
避避风港3 小时前
Java 抽象类
java·开发语言·python
cookies_s_s4 小时前
C++20 协程
linux·开发语言·c++
石油人单挑所有4 小时前
C语言知识体系梳理-第一篇
c语言·开发语言
hetao17338374 小时前
2025-11-13~14 hetao1733837的刷题记录
c++·算法
把csdn当日记本的菜鸡4 小时前
js查缺补漏
开发语言·javascript·ecmascript