前言

或许大家多少会有点疑问,智能指针是什么,和普通指针有什么区别呢,为啥要学智能指针。没关系看完这篇文章,大家的问题就能迎刃而解了。话不多说,请诸君品鉴

在C++编程中,动态内存管理(new/delete)是绕不开的话题,但手动管理内存极易引发内存泄漏、野指针、重复释放等问题。智能指针作为C++标准库提供的“内存管家”,基于RAII(资源获取即初始化)机制,能自动管理动态内存,是现代C++开发中替代裸指针的核心工具。本文将从原理、用法、实战场景等维度,全面解析智能指针的核心知识点。


一、什么是智能指针?

智能指针是 C++ 标准库(头文件)提供的类模板,本质是对普通裸指针的封装,
重载了*(解引用)和->(成员访问)运算符,
行为上兼容普通指针,但内置了自动内存管理逻辑 —— (在智能指针对象生命周期结束(析构)时,自动调用delete/delete[]释放管理的动态内存,无需程序员手动干预。)

核心作用:
解决裸指针的三大痛点
1.忘记释放内存导致内存泄漏;
2.重复释放内存导致未定义行为;
3.指针悬空(野指针)访问已释放的内存。

二、智能指针的本质与核心特点

2.1本质

智能指针不是 “指针”,而是封装了裸指针的类对象
构造时接收动态内存的裸指针(如new int(10)),接管内存所有权;
析构时自动执行内存释放逻辑(delete/delete[]);
重载*和->,让用户像使用普通指针一样使用它

2.2核心特点

特性 说明
RAII 机制 资源(动态内存)的获取与智能指针对象的初始化绑定,析构时自动释放资源
所有权语义 不同智能指针的所有权规则(独占 / 共享)不同,决定使用场景
类型安全 编译期检查指针类型,避免裸指针的类型混乱
自动清理 无需手动调用 delete,即使程序抛出异常,也能保证内存释放

三、智能指针的分类与写法

3.1独占型智能指针:std::unique_ptr /(unique_prt)

核心规则
独占动态内存所有权,同一时间只有一个unique_ptr指向该内存;
不可拷贝,仅可通过std::move转移所有权;
支持管理数组(unique_ptr<T[]>),自动调用delete[]。

代码实例:

#include <memory>
#include <iostream>
using namespace std;

int main() {
    // 1. 管理单个对象
    unique_ptr<int> up1(new int(10)); // 初始化方式1:直接构造
    unique_ptr<int> up2 = make_unique<int>(20); // 推荐:make_unique(C++14+),避免裸new
    
    // 2. 管理数组
    unique_ptr<int[]> up3(new int[5]); // 数组类型,自动delete[]
    for (int i = 0; i < 5; ++i) {
        up3[i] = i; // 像普通数组一样访问
    }

    // 3. 转移所有权(不可直接拷贝)
    unique_ptr<int> up4 = move(up1); // up1失去所有权,变为空
    // unique_ptr<int> up5 = up4; // 错误:不可拷贝

    // 4. 常用方法
    if (up4) { // 判空,等价于 up4 != nullptr
        cout << *up4 << endl; // 输出20
    }
    up4.reset(); // 手动释放内存,up4变为空
    return 0;
}

在这里其实在之前还有一个是auto_ptr,但是现在已经被弃用了,大家如果想了解的话可以去自行搜索

那么具体的应用场景又是什么
如下:
独占资源:
函数返回动态分配的对象(避免拷贝,转移所有权);
局部动态对象(如临时创建的类实例);
容器中存储独占对象(如vector<unique_ptr>)

实例:

#include <memory>
#include <string>
using namespace std;

class Person {
private:
    string name;
public:
    Person(string n) : name(n) {}
    void show() { cout << "姓名:" << name << endl; }
};

// 返回unique_ptr,自动转移所有权
unique_ptr<Person> createPerson(string name) {
    return make_unique<Person>(name); // C++14+支持make_unique
}

int main() {
    auto p = createPerson("张三");
    p->show(); // 输出:姓名:张三
    return 0;
}

3.2共享型智能指针:std::shared_ptr /(shared_ptr)

核心规则
共享动态内存所有权,多个shared_ptr可指向同一内存;
内部维护引用计数:拷贝时计数 + 1,析构时计数 - 1;
当引用计数为 0 时,自动释放管理的内存。

实例写法:

#include <memory>
#include <iostream>
using namespace std;

int main() {
    // 1. 初始化(推荐make_shared,减少内存分配次数)
    shared_ptr<int> sp1 = make_shared<int>(30);
    shared_ptr<int> sp2(sp1); // 拷贝,引用计数变为2

    // 2. 查看引用计数
    cout << "引用计数:" << sp1.use_count() << endl; // 输出2

    // 3. 管理数组(需自定义删除器,默认delete不支持数组)
    shared_ptr<int> sp3(new int[5], [](int* p) { delete[] p; });

    // 4. 重置指针
    sp2.reset(); // 引用计数变为1
    sp1.reset(); // 引用计数变为0,内存释放

    return 0;
}

使用:
共享资源场景
多个对象共享同一数据(如多线程共享配置对象);
复杂数据结构(如链表、树节点,多个节点指向同一父节点);
需要长期共享的动态对象。
实例:

#include <memory>
#include <iostream>
using namespace std;

// 共享的数据类
class Data {
public:
    int value = 100;
    ~Data() { cout << "Data 析构" << endl; }
};

class A {
public:
    shared_ptr<Data> data_ptr;
    A(shared_ptr<Data> p) : data_ptr(p) {}
};

class B {
public:
    shared_ptr<Data> data_ptr;
    B(shared_ptr<Data> p) : data_ptr(p) {}
};

int main() {
    shared_ptr<Data> d = make_shared<Data>();
    A a(d);
    B b(d);
    cout << "引用计数:" << d.use_count() << endl; // 输出3
    // 析构时,计数减至0,Data自动析构
    return 0;
}

3.3弱引用智能指针:std::weak_ptr / (weak_ptr)

核心规则
不拥有内存所有权,仅 “观察”shared_ptr管理的内存;
拷贝 / 析构不影响shared_ptr的引用计数;
解决shared_ptr的循环引用问题,不能直接解引用,需通过**lock()**转为shared_ptr后使用。

实例使用:

#include <memory>
#include <iostream>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(40);
    weak_ptr<int> wp(sp); // 弱引用,引用计数仍为1

    // 1. 检查对象是否存活
    if (!wp.expired()) {
        // 2. 转为shared_ptr访问对象
        shared_ptr<int> sp_temp = wp.lock();
        if (sp_temp) {
            cout << *sp_temp << endl; // 输出40
        }
    }

    sp.reset(); // 内存释放,wp变为过期
    cout << "是否过期:" << boolalpha << wp.expired() << endl; // 输出true
    return 0;
}

使用:

解决循环引用场景
shared_ptr的循环引用会导致引用计数无法归 0,内存泄漏,weak_ptr可打破循环。

实例:

#include <memory>
#include <iostream>
using namespace std;

class B; // 前向声明

class A {
public:
    weak_ptr<B> b_ptr; // 改为weak_ptr,不增加引用计数
    ~A() { cout << "A 析构" << endl; }
};

class B {
public:
    shared_ptr<A> a_ptr;
    ~B() { cout << "B 析构" << endl; }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    // 析构时,b的计数为1→销毁,a的计数为0→销毁,无内存泄漏
    return 0;
}

四、智能指针 vs 普通裸指针

维度 普通裸指针 智能指针
内存管理 手动 new 分配、delete 释放 自动释放(析构时)
所有权 无明确规则 有明确所有权(独占 / 共享)
安全性 易内存泄漏、野指针 避免大部分内存管理错误
拷贝行为 简单拷贝地址 unique_ptr 不可拷贝,shared_ptr 拷贝加计数
功能扩展 仅地址访问 提供 reset()use_count() 等方法

五、智能指针如何实现自动清理内存?

核心是RAII(资源获取即初始化)机制,
底层逻辑如下:
初始化阶段:智能指针对象构造时,接收动态内存的裸指针,接管内存所有权;
生命周期阶段:智能指针对象存于栈上(或作为其他对象的成员),遵循栈对象的生命周期规则;
析构阶段:当智能指针对象超出作用域(如函数结束、对象析构),编译器自动调用其析构函数,析构函数内执行delete/delete[]释放管理的内存。

演示:

// 仅用于演示原理,非标准实现
template <typename T>
class MyUniquePtr {
private:
    T* ptr; // 封装的裸指针
public:
    // 构造:获取资源
    explicit MyUniquePtr(T* p = nullptr) : ptr(p) {}

    // 析构:释放资源
    ~MyUniquePtr() {
        delete ptr; // 自动释放
    }

    // 重载解引用运算符
    T& operator*() const {
        return *ptr;
    }

    // 禁用拷贝(独占所有权)
    MyUniquePtr(const MyUniquePtr&) = delete;
    MyUniquePtr& operator=(const MyUniquePtr&) = delete;

    // 移动构造(转移所有权)
    MyUniquePtr(MyUniquePtr&& other) noexcept {
        ptr = other.ptr;
        other.ptr = nullptr;
    }
};

总结

智能指针是基于 RAII 机制的类模板,核心是自动管理动态内存,避免内存泄漏;
unique_ptr独占所有权(不可拷贝、可移动),shared_ptr共享所有权(引用计数),weak_ptr解决循环引用;
优先使用make_unique/make_shared初始化智能指针,避免裸new;

以上就是基本的知识总结了,如果有表述的不明白的,请谅解,也欢迎大家在评论区进行交流

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐