智能指针使用场景

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

      • 一、`unique_ptr`:独占所有权(推荐优先使用)
        • [1. 函数返回动态分配的对象(替代裸指针)](#1. 函数返回动态分配的对象(替代裸指针))
        • [2. 管理类的独占成员资源](#2. 管理类的独占成员资源)
        • [3. 存储多态对象的容器(多态场景首选)](#3. 存储多态对象的容器(多态场景首选))
        • [4. 管理动态数组(避免`delete`/`delete[]`混淆)](#4. 管理动态数组(避免delete/delete[]混淆))
        • [5. 局部动态对象的异常安全](#5. 局部动态对象的异常安全)
      • 二、`shared_ptr`:共享所有权(引用计数)
        • [1. 多个对象共享同一个资源](#1. 多个对象共享同一个资源)
        • [2. 跨作用域共享对象](#2. 跨作用域共享对象)
        • [3. 管理非内存资源(自定义删除器)](#3. 管理非内存资源(自定义删除器))
        • 注意:优先使用`make_shared`创建`shared_ptr`
      • 三、`weak_ptr`:弱引用(配合`shared_ptr`)
        • [1. 解决`shared_ptr`的循环引用(核心场景)](#1. 解决shared_ptr的循环引用(核心场景))
        • [2. 观察共享资源的存在性(不影响资源释放)](#2. 观察共享资源的存在性(不影响资源释放))
        • [3. 回调函数中避免`this`指针的循环引用](#3. 回调函数中避免this指针的循环引用)
      • 四、使用智能指针的注意事项
      • 总结

C++智能指针的核心是RAII(资源获取即初始化) 机制,它能自动管理动态内存的生命周期,解决裸指针带来的内存泄漏、野指针、double free、异常安全 等问题。C++11及以上版本中,主流的智能指针有unique_ptr(独占所有权)、shared_ptr(共享所有权)、weak_ptr(弱引用,配合shared_ptr使用),废弃了老旧的auto_ptr

选择智能指针的核心原则是:根据所有权语义选择 (独占/共享),优先使用轻量级的unique_ptr,仅在需要共享时使用shared_ptrweak_ptr仅作为辅助解决循环引用或观察资源。

下面分场景详细说明各智能指针的使用场景:

一、unique_ptr:独占所有权(推荐优先使用)

unique_ptr轻量级 智能指针(无额外内存开销,仅封装裸指针),具有移动语义 (可通过std::move转移所有权),但不可拷贝 ,代表对资源的独占所有权。其适用场景如下:

1. 函数返回动态分配的对象(替代裸指针)

场景:工厂模式、创建动态对象的函数,返回裸指针时调用者容易忘记delete导致内存泄漏,而unique_ptr可自动释放。

cpp 复制代码
#include <memory>
#include <iostream>

class Product {
public:
    ~Product() { std::cout << "Product destroyed\n"; }
};

// 工厂函数:返回独占的动态对象
std::unique_ptr<Product> createProduct() {
    // 直接返回,编译器会自动优化移动操作
    return std::unique_ptr<Product>(new Product());
    // C++14可使用make_unique,更安全
    // return std::make_unique<Product>();
}

int main() {
    auto product = createProduct(); // 接收对象,自动管理生命周期
    return 0; // 函数结束,product析构,释放Product
}
2. 管理类的独占成员资源

场景:类的成员变量是动态分配的资源(如网络连接、文件句柄、自定义资源),使用unique_ptr作为成员,析构时自动释放,无需手动写析构函数。

cpp 复制代码
class Resource {
public:
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

class MyClass {
private:
    // 独占Resource,MyClass析构时自动释放
    std::unique_ptr<Resource> res;
public:
    MyClass() : res(std::make_unique<Resource>()) {}
};

int main() {
    MyClass obj; // 析构时,obj的res成员自动释放Resource
    return 0;
}
3. 存储多态对象的容器(多态场景首选)

场景:需要存储基类指针指向派生类对象的容器(如vector<Base*>),使用unique_ptr既支持多态,又能自动管理生命周期,且开销远小于shared_ptr

cpp 复制代码
#include <vector>

class Base {
public:
    virtual ~Base() { std::cout << "Base destroyed\n"; }
};

class Derived : public Base {
public:
    ~Derived() override { std::cout << "Derived destroyed\n"; }
};

int main() {
    // 存储多态对象的容器,独占所有权
    std::vector<std::unique_ptr<Base>> polyVec;
    polyVec.push_back(std::make_unique<Derived>());
    polyVec.push_back(std::make_unique<Derived>());
    // 容器析构时,所有元素自动释放,调用派生类析构
    return 0;
}
4. 管理动态数组(避免delete/delete[]混淆)

场景:动态数组的管理,unique_ptr<T[]>专门支持数组,自动调用delete[],而裸指针容易因deletedelete[]混淆导致内存泄漏或崩溃。

cpp 复制代码
int main() {
    // 管理动态数组,析构时调用delete[]
    std::unique_ptr<int[]> arr(new int[10]{1,2,3});
    arr[0] = 10; // 支持[]运算符
    return 0; // 自动释放数组
}
5. 局部动态对象的异常安全

场景:局部动态对象创建后,若中间代码抛出异常,裸指针的delete无法执行导致内存泄漏;unique_ptr在析构时(即使异常)会自动释放资源。

cpp 复制代码
void riskyFunc() {
    // 裸指针:若下面抛出异常,obj无法被delete
    // Product* obj = new Product();
    // 智能指针:即使抛异常,obj析构时释放
    std::unique_ptr<Product> obj = std::make_unique<Product>();
    // 模拟抛出异常
    throw std::runtime_error("something wrong");
}

int main() {
    try {
        riskyFunc();
    } catch (const std::exception& e) {
        std::cout << e.what() << '\n';
    }
    return 0;
}

二、shared_ptr:共享所有权(引用计数)

shared_ptr通过引用计数 实现共享所有权,多个shared_ptr可指向同一个对象,最后一个shared_ptr销毁时,对象才会被释放。其有额外开销(存储引用计数的控制块),支持拷贝,适用场景如下:

1. 多个对象共享同一个资源

场景:多个类实例、模块需要共享同一个资源(如缓存、数据源、配置对象),使用shared_ptr保证资源在最后一个使用者销毁时释放。

cpp 复制代码
class Cache {
public:
    ~Cache() { std::cout << "Cache destroyed\n"; }
    void read() { std::cout << "Cache read\n"; }
};

// 缓存使用者:共享同一个Cache
class CacheUser {
private:
    std::shared_ptr<Cache> cache;
public:
    CacheUser(std::shared_ptr<Cache> c) : cache(c) {}
    void useCache() { cache->read(); }
};

int main() {
    auto cache = std::make_shared<Cache>(); // 引用计数=1
    CacheUser user1(cache); // 引用计数=2
    CacheUser user2(cache); // 引用计数=3
    user1.useCache();
    // user1、user2、cache依次销毁,引用计数减至0,Cache释放
    return 0;
}
2. 跨作用域共享对象

场景:对象需要在多个函数、作用域间传递,且每个作用域都需要访问该对象,shared_ptr可保证对象在所有使用者都结束后释放。

cpp 复制代码
void processObj(std::shared_ptr<Product> p) {
    // 引用计数+1
    std::cout << "Processing obj, ref count: " << p.use_count() << '\n';
}

int main() {
    auto p = std::make_shared<Product>(); // 引用计数=1
    std::cout << "Before process, ref count: " << p.use_count() << '\n';
    processObj(p); // 传递后引用计数=2,函数结束后减为1
    return 0; // p销毁,引用计数=0,Product释放
}
3. 管理非内存资源(自定义删除器)

场景:除了动态内存,还可管理文件句柄、socket、数据库连接等资源,通过自定义删除器 替代delete,实现资源的自动释放。

cpp 复制代码
#include <cstdio>

// 管理文件句柄,自定义删除器为fclose
int main() {
    std::shared_ptr<FILE> fp(fopen("test.txt", "r"), [](FILE* f) {
        if (f) fclose(f);
        std::cout << "File closed\n";
    });
    if (fp) {
        // 操作文件
    }
    return 0; // fp析构,调用自定义删除器关闭文件
}
注意:优先使用make_shared创建shared_ptr

make_shared比直接new更高效(一次分配对象和控制块的内存,而shared_ptr(new T)需要两次分配),且更安全(避免异常导致的内存泄漏)。

三、weak_ptr:弱引用(配合shared_ptr

weak_ptr弱引用 ,不拥有资源,也不增加shared_ptr的引用计数,仅用于观察shared_ptr管理的资源。其核心作用是解决shared_ptr循环引用问题,也可用于观察资源是否存在。

1. 解决shared_ptr的循环引用(核心场景)

场景:两个对象互相持有shared_ptr,导致引用计数无法归零,内存泄漏。将其中一方改为weak_ptr即可打破循环。

cpp 复制代码
class Parent;
class Child;

class Parent {
public:
    // 持有子对象的共享指针
    std::shared_ptr<Child> child;
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

class Child {
public:
    // 改为弱引用,不增加Parent的引用计数
    std::weak_ptr<Parent> parent;
    ~Child() { std::cout << "Child destroyed\n"; }

    // 访问父对象(需要先lock())
    void accessParent() {
        if (auto p = parent.lock()) { // lock()返回shared_ptr,若对象存在则有效
            std::cout << "Parent exists\n";
        } else {
            std::cout << "Parent destroyed\n";
        }
    }
};

int main() {
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    parent->child = child; // Parent的引用计数=1,Child的引用计数=2
    child->parent = parent; // Parent的引用计数仍为1(weak_ptr不增加)
    child->accessParent();
    // parent、child销毁:Child的引用计数减为1→0,释放Child;Parent的引用计数减为0,释放Parent
    return 0;
}
2. 观察共享资源的存在性(不影响资源释放)

场景:缓存系统中,缓存对象用shared_ptr管理,观察者用weak_ptr指向缓存对象,需要时通过lock()获取shared_ptr(若缓存未释放则可用,否则返回空),避免观察者持有资源导致其无法被释放。

cpp 复制代码
// 缓存管理器
class CacheManager {
private:
    std::shared_ptr<Cache> cache;
public:
    void setCache(std::shared_ptr<Cache> c) { cache = c; }
    // 返回弱引用,观察者不影响缓存的释放
    std::weak_ptr<Cache> getCacheWeak() { return cache; }
};

int main() {
    CacheManager manager;
    manager.setCache(std::make_shared<Cache>());
    // 观察者获取弱引用
    auto cacheWeak = manager.getCacheWeak();
    // 检查缓存是否存在
    if (auto cache = cacheWeak.lock()) {
        cache->read();
    } else {
        std::cout << "Cache not exists\n";
    }
    // 清空缓存
    manager.setCache(nullptr);
    if (auto cache = cacheWeak.lock()) {
        cache->read();
    } else {
        std::cout << "Cache not exists\n";
    }
    return 0;
}
3. 回调函数中避免this指针的循环引用

场景:类的成员函数作为回调函数时,若回调管理器持有shared_ptr<Class>,而类又持有回调管理器的shared_ptr,会导致循环引用。此时用weak_ptr包裹this(需类继承std::enable_shared_from_this),避免循环。

cpp 复制代码
#include <functional>
#include <vector>

class CallbackManager {
private:
    std::vector<std::function<void()>> callbacks;
public:
    void addCallback(std::function<void()> cb) {
        callbacks.push_back(cb);
    }
    void runCallbacks() {
        for (auto& cb : callbacks) {
            cb();
        }
    }
};

// 继承enable_shared_from_this,才能获取this的shared_ptr
class MyClass : public std::enable_shared_from_this<MyClass> {
private:
    std::shared_ptr<CallbackManager> manager;
public:
    MyClass(std::shared_ptr<CallbackManager> m) : manager(m) {
        registerCallback();
    }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }

    void registerCallback() {
        // 用weak_ptr包裹this,避免循环引用
        std::weak_ptr<MyClass> self = shared_from_this();
        manager->addCallback([self]() {
            // lock()检查对象是否存在
            if (auto p = self.lock()) {
                p->callbackFunc();
            }
        });
    }

    void callbackFunc() {
        std::cout << "Callback executed\n";
    }
};

int main() {
    auto manager = std::make_shared<CallbackManager>();
    auto obj = std::make_shared<MyClass>(manager);
    manager->runCallbacks();
    return 0;
}

四、使用智能指针的注意事项

  1. 不要管理栈上的对象 :智能指针的析构函数会调用delete,若指向栈上对象,会导致double free(栈对象由系统自动释放)。

    cpp 复制代码
    Product obj;
    // 错误:指向栈对象,析构时会delete &obj,导致崩溃
    // std::unique_ptr<Product> p(&obj);
  2. 不要将同一个裸指针交给多个智能指针管理 :会导致多个智能指针各自释放同一个对象,引发double free

    cpp 复制代码
    Product* raw = new Product();
    // 错误:p1和p2都管理raw,析构时两次delete raw
    // std::unique_ptr<Product> p1(raw);
    // std::unique_ptr<Product> p2(raw);
  3. 避免shared_ptr的循环引用 :一旦出现循环引用,必须用weak_ptr打破。

  4. shared_ptr的线程安全shared_ptr引用计数操作是线程安全的 ,但对象的访问不是,多线程访问对象时需加锁同步。

  5. shared_ptr管理动态数组需自定义删除器shared_ptr默认调用delete,管理数组时需手动指定删除器(unique_ptr<T[]>更方便)。

    cpp 复制代码
    // 正确:shared_ptr管理数组,自定义删除器
    std::shared_ptr<int> arr(new int[10], [](int* p) { delete[] p; });

总结

智能指针的使用需遵循所有权语义

  • 独占所有权 :优先使用unique_ptr(轻量级、高效);
  • 共享所有权 :使用shared_ptr(引用计数,有开销);
  • 观察共享资源/解决循环引用 :使用weak_ptr(配合shared_ptr)。

在所有需要动态分配内存(new/new[])的场景,都应优先使用智能指针替代裸指针,以提升代码的安全性和可维护性。

相关推荐
zhuqiyua5 小时前
第一次课程家庭作业
c++
只是懒得想了5 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
m0_736919106 小时前
模板编译期图算法
开发语言·c++·算法
玖釉-6 小时前
深入浅出:渲染管线中的抗锯齿技术全景解析
c++·windows·图形渲染
【心态好不摆烂】6 小时前
C++入门基础:从 “这是啥?” 到 “好像有点懂了”
开发语言·c++
dyyx1116 小时前
基于C++的操作系统开发
开发语言·c++·算法
AutumnorLiuu6 小时前
C++并发编程学习(一)——线程基础
开发语言·c++·学习
m0_736919106 小时前
C++安全编程指南
开发语言·c++·算法
阿猿收手吧!6 小时前
C++ std::lock与std::scoped_lock深度解析:从死锁解决到安全实践
开发语言·c++
2301_790300966 小时前
C++符号混淆技术
开发语言·c++·算法