c++智能指针是raii(资源获取即初始化)思想的核心实践,用于自动管理动态内存,解决传统裸指针的内存泄漏、悬垂指针、重复释放等问题。
c++11及后续标准提供了多种智能指针,核心区别在于所有权模型(独占/共享/弱引用),定义在<memory> 头文件中。
一、智能指针的分类与核心特性
c++标准库中的智能指针主要有4种(auto_ptr已被废弃):
类型 | 所有权模型 | 核心特点 |
|---|---|---|
auto_ptr | 独占(已废弃) | 拷贝时转移所有权,易导致意外失效,禁止使用 |
unique_ptr | 独占所有权 | 同一时间仅一个指针拥有对象,不可拷贝,仅可移动 |
shared_ptr | 共享所有权 | 多个指针共享对象,通过引用计数跟踪所有者数量,计数归零时释放对象 |
weak_ptr | 弱引用(无所有权) | 指向shared_ptr管理的对象,但不增加引用计数,用于解决循环引用 |
二、各智能指针详解
1.unique_ptr:独占所有权的轻量级指针
unique_ptr是最常用的智能指针之一,适用于单一所有者的场景(如动态数组、局部资源管理)。
核心特性:
- 独占性:不能拷贝(
copy constructor/copy assignment被删除),只能通过std::move转移所有权。 - 轻量:大小与裸指针相同(无额外开销,除非自定义删除器)。
- 支持数组:
unique_ptr<t[]>重载了operator[],自动用delete[]释放。 - 自定义删除器:可指定任意资源释放逻辑(如文件句柄、网络连接)。
用法示例:
#include <memory>
#include <iostream>
int main() {
// 1. 创建:优先用make_unique(c++14引入,更安全高效)
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// 等价于:std::unique_ptr<int> p1(new int(42));
// 2. 访问对象:用*或->
std::cout << *p1 << std::endl; // 输出42
// 3. 移动所有权(不可拷贝)
std::unique_ptr<int> p2 = std::move(p1); // p1失去所有权,变为nullptr
if (!p1) std::cout << "p1 is null" << std::endl;
// 4. 数组版本
std::unique_ptr<int[]> arr = std::make_unique<int[]>(3);
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// 5. 释放所有权:release()返回裸指针,不再管理
int* raw = p2.release();
delete raw; // 需手动释放(不推荐,尽量用智能指针全程管理)
// 6. 重置:reset()释放当前对象,可选新对象
p2.reset(new int(100)); // 先释放原对象(若有),再管理新对象
p2.reset(); // 释放对象,变为nullptr
return 0;
}自定义删除器示例(管理文件句柄):
#include <cstdio>
#include <memory>
// 自定义删除器:关闭文件
struct filedeleter {
void operator()(file* fp) const {
if (fp) std::fclose(fp);
}
};
int main() {
// 用自定义删除器创建unique_ptr
std::unique_ptr<file, filedeleter> fp(std::fopen("test.txt", "w"));
if (fp) std::fputs("hello", fp.get()); // get()返回裸指针
// 离开作用域时,自动调用filedeleter释放文件
return 0;
}2.shared_ptr:共享所有权的引用计数指针
shared_ptr适用于多所有者的场景(如容器中存储动态对象、跨模块共享资源)。
核心特性:
- 共享性:多个
shared_ptr可指向同一对象,通过引用计数跟踪所有者数量。 - 引用计数:每次拷贝
shared_ptr,计数+1;析构或reset()时,计数-1;计数归零时,释放对象。 - 高效创建:优先用
std::make_shared(c++11引入),一次性分配对象内存和控制块(含引用计数、弱引用计数),比直接new更高效。 - 线程安全:引用计数的增减是原子操作(多线程安全),但对象本身的访问需额外同步(如互斥锁)。
用法示例:
#include <memory>
#include <iostream>
int main() {
// 1. 创建:优先用make_shared(更高效)
auto p1 = std::make_shared<int>(42);
std::cout << "use_count: " << p1.use_count() << std::endl; // 输出1
// 2. 共享所有权:拷贝时计数+1
auto p2 = p1;
std::cout << "use_count: " << p1.use_count() << std::endl; // 输出2
// 3. 释放所有权:reset()减少计数
p1.reset();
std::cout << "use_count after p1.reset(): " << p2.use_count() << std::endl; // 输出1
// 4. 自定义删除器(管理动态数组)
auto arr = std::shared_ptr<int>(new int[3], [](int* p) { delete[] p; });
arr.get()[0] = 1; // 访问数组元素
return 0;
}注意:make_sharedvs 直接new
make_shared的优势:
- 更安全:避免裸指针暴露(如
func(std::shared_ptr<int>(new int), func2())可能因编译器优化导致内存泄漏); - 更高效:一次性分配对象和控制块,减少内存碎片;
- 异常安全:构造过程中若抛出异常,
make_shared不会泄漏内存。
例外情况:需要自定义删除器或延迟初始化时,只能用new。
3.weak_ptr:弱引用指针(解决循环引用)
weak_ptr是shared_ptr的“观察者”,不拥有对象所有权,仅指向shared_ptr管理的对象,用于解决循环引用问题。
核心问题:循环引用
当两个shared_ptr互相指向对方时,引用计数永远无法归零,导致内存泄漏:
#include <memory>
struct a { std::shared_ptr<b> b; };
struct b { std::shared_ptr<a> a; };
int main() {
auto a = std::make_shared<a>();
auto b = std::make_shared<b>();
a->b = b; // a的b指向b,b的计数+1(变为2)
b->a = a; // b的a指向a,a的计数+1(变为2)
// 离开作用域时,a和b的计数各减1(变为1),无法释放!
return 0;
}weak_ptr的解决方案
将其中一个shared_ptr改为weak_ptr(弱引用,不增加计数):
struct a { std::weak_ptr<b> b; }; // 弱引用,不增加b的计数
struct b { std::weak_ptr<a> a; }; // 弱引用,不增加a的计数
int main() {
auto a = std::make_shared<a>();
auto b = std::make_shared<b>();
a->b = b; // b的计数仍为1(weak_ptr不增加)
b->a = a; // a的计数仍为1(weak_ptr不增加)
// 离开作用域时,a和b的计数各减1(变为0),正常释放!
return 0;
}weak_ptr的用法:
- 创建:由
shared_ptr构造,或通过lock()获取shared_ptr。 - 访问对象:必须用
lock()转换为shared_ptr(若对象已释放,返回空shared_ptr)。 - 检查有效性:用
expired()判断对象是否已释放(use_count() == 0)。
示例:
#include <memory>
#include <iostream>
int main() {
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 弱引用指向sp的对象
// 1. 检查有效性
if (!wp.expired()) {
std::cout << "object exists" << std::endl;
}
// 2. 访问对象:lock()获取shared_ptr
auto locked_sp = wp.lock();
if (locked_sp) {
std::cout << *locked_sp << std::endl; // 输出42
}
// 3. 释放原对象
sp.reset();
if (wp.expired()) {
std::cout << "object has been released" << std::endl;
}
return 0;
}三、智能指针的关键注意事项
- 禁止用裸指针初始化多个智能指针:
- 会导致多个智能指针独立管理同一对象,析构时重复释放。
- ❌ 错误:
int* p = new int; auto p1 = std::shared_ptr<int>(p); auto p2 = std::shared_ptr<int>(p); - ✅ 正确:
auto p1 = std::make_shared<int>(42); auto p2 = p1;
- 优先用make_shared/make_unique:
- 避免裸指针暴露,提升安全性和效率(见前文)。
- 避免循环引用:
- 用
weak_ptr打破shared_ptr之间的双向依赖(见前文)。
- 用
- 不要手动delete智能指针的get()结果:
get()返回的裸指针是智能指针内部管理的,手动delete会导致双重释放。
- 线程安全:
shared_ptr的引用计数是线程安全的(原子操作);shared_ptr的对象本身不是线程安全的(多线程访问需同步);unique_ptr不支持多线程(独占性)。
四、智能指针的选择指南
场景 | 推荐智能指针 |
|---|---|
单一所有者(如局部变量) | unique_ptr |
多所有者(如容器共享) | shared_ptr |
解决循环引用 | weak_ptr |
管理数组 | unique_ptr<t[]> |
管理非内存资源(文件、socket) | 带自定义删除器的unique_ptr/shared_ptr |
五、总结
智能指针是c++现代编程的必备工具,核心是通过所有权模型自动管理内存。记住:
- 独占用
unique_ptr,共享用shared_ptr; - 循环引用用
weak_ptr破局; - 优先用
make_shared/make_unique; - 避免裸指针与智能指针混用。
掌握智能指针能大幅减少内存错误,提升代码的健壮性和可维护性——这也是嵌入式linux开发中(如驱动、qt应用、流媒体资源管理)的关键技能。
到此这篇关于c++各种智能指针的使用小结的文章就介绍到这了,更多相关c++各种智能指针内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论