C++中的智能指针
文章目录
前言
或许大家多少会有点疑问,智能指针是什么,和普通指针有什么区别呢,为啥要学智能指针。没关系看完这篇文章,大家的问题就能迎刃而解了。话不多说,请诸君品鉴
在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;
以上就是基本的知识总结了,如果有表述的不明白的,请谅解,也欢迎大家在评论区进行交流
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)