C++ 智能指针,指针用于保存内存地址,在动态分配中不可或缺,但也容易出错。下面代码就演示了典型的内存泄漏:
while (1) {
int* ptr = new int; // 只申请,从不释放
}
每次循环都丢失一块堆内存,程序最终会因耗尽内存而崩溃。
一. 智能指针
为了杜绝这类问题,C++ 提供了“智能指针”:它们封装裸指针,自动释放所管理的资源,从根本上避免泄漏。所有智能指针都定义在头文件 <memory> 中,采用模板实现,可托管任意类型。
标准库给出的智能指针种类:
auto_ptr(C++11 起废弃,C++17 移除)unique_ptr——独占式所有权shared_ptr——共享式所有权weak_ptr——非拥有式“弱”引用
1. auto_ptr(已废弃)
auto_ptr 是 C++ 早期提供的智能指针,它在离开作用域时会自动 delete 所指向的对象,从而防止内存泄漏。
语法
auto_ptr<类型> 名字;
示例:
#include <iostream>
#include <memory>
using namespace std;
int main() {
auto_ptr<int> ptr1(new int(10)); // 托管一块 int
cout << *ptr1 << endl; // 输出 10
auto_ptr<int> ptr2 = ptr1; // 所有权转移给 ptr2
cout << *ptr2; // 仍输出 10
// ptr1 现已为空,再解引用会崩溃
return 0;
}
注意
auto_ptr 在 C++11 起被弃用,C++17 开始彻底移除。 现代代码请改用 unique_ptr 或 shared_ptr。
2. unique_ptr 独占式所有权
unique_ptr 同一时间独占一份指针,禁止拷贝。 如需转移托管权,必须显式使用 std::move() 把所有权交给另一个 unique_ptr。
unique_ptr 是 C++11 引入的智能指针,定义在头文件 <memory> 中,用于管理动态分配的对象。核心要点如下:
- 独占所有权:同一时刻只能有一个
unique_ptr拥有指定对象。 - 仅可移动,不可拷贝:通过
std::move可把所有权转移给另一个unique_ptr。 - 自动释放:当
unique_ptr离开作用域或被重置时,会自动delete所指向的对象,无需手动释放。
2.1 语法
unique_ptr<A> ptr1 (new A); //"A为数据类型"
执行时会在堆上创建对象,并让 指针名 成为其唯一所有者;指针销毁时对象同步被销毁。
自 C++14 起,推荐用更安全的方式创建:
unique_ptr<A> ptr1 = make_unique<A>(); //"A为数据类型"
2.2 unique_ptr 示例
2.2.1 创建并使用
- 先定义一个结构体
A,里面有个成员函数printA()用来打印提示文字。 - 在
main()里用unique_ptr<A>创建动态对象,p1是唯一拥有者。 - 通过
p1->printA()调用成员函数;p1.get()可取出裸指针地址。
代码:
#include <iostream>
#include <memory>
using namespace std;
struct A {
void printA() {
cout << "A struct...." << endl;
}
};
int main() {
unique_ptr<A> p1(new A); // 创建动态对象,p1 独占
p1->printA(); // 调用成员函数
cout << p1.get(); // 输出裸指针地址(如 0x3e9dec20)
return 0;
}
输出:
A struct....
0x3e9dec20
2.2.2 试图拷贝 unique_ptr 所拥有对象
#include <iostream>
#include <memory>
using namespace std;
struct A {
void printA() {
cout << "A struct...." << endl;
}
};
int main() {
unique_ptr<A> p1(new A);
p1->printA();
//显示所指向指针的地址
cout << p1.get() << endl;
// 会导致编译时错误
unique_ptr<A> p2 = p1;
p2->printA();
return 0;
}
main.cpp: In function ‘int main()’:
main.cpp:18:24: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)
[with _Tp = A; _Dp = std::default_delete]’
18 | unique_ptr<A> p2 = p1;
| ^~
In file included from /usr/include/c++/11/memory:76,
from main.cpp:3:
/usr/include/c++/11/bits/unique_ptr.h:468:7: note: declared here
468 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
上面的代码会产生编译错误,因为我们不能对 unique_ptr 进行普通拷贝赋值。要让 p2 接管 p1 的对象,必须使用移动语义,如下所示:
2.2.3 转移 unique_ptr 的所有权
#include <iostream>
#include <memory>
using namespace std;
struct A {
void printA() {
cout << "A struct...." << endl;
}
};
int main() {
unique_ptr<A> p1(new A);
p1->printA();
// displays address of the containing pointer
cout << p1.get() << endl;
// now address stored in p1 shpould get copied to p2
unique_ptr<A> p2 = move(p1);
p2->printA();
cout << p1.get() << endl;
cout << p2.get();
return 0;
}
输出结果:
A struct....
0x3514c20
A struct....
0
0x3514c20
代码演示了如何用 `std::move` 把 `p1` 的托管权转给 `p2`:
1. 一开始 `p1` 指向动态创建的 `A` 对象,地址为 `0x3514c20`。
2. 执行 “unique_ptr<A> p2 = move(p1)” 后,所有权被转移:
- `p1.get()` 变成 `0`(空指针)
- `p2.get()` 仍保持原地址 `0x3514c20`
结论:通过移动语义,`p1` 把地址“交出去”后自身置空,`p2` 成为新的唯一所有者。
2.3 什么时候用 unique_ptr?
当你需要独占资源所有权时就用它。 unique_ptr 保证同一时刻只有一个指针拥有这份资源,禁止拷贝,只允许通过 std::move 转移所有权;对象作用域结束时会自动 delete,彻底杜绝内存泄漏。
3. shared_ptr 共享式所有权
使用 shared_ptr 可以让多个指针同时指向同一个对象,并通过内部的引用计数机制(可用use_count() 查看)来跟踪当前有多少个 shared_ptr 共享该资源;当最后一个指针销毁时,对象才会被自动 delete。
C++ 中的 shared_ptr。std::shared_ptr 是 C++11 引入的智能指针之一。与裸指针不同,它内部带有一个控制块,用来记录被管理对象的引用计数。所有指向同一对象的 shared_ptr 副本共享这份计数,确保当最后一个副本销毁时才自动 delete 对象,实现安全的内存管理。
3.1 语法
声明一个管理类型 T 的 shared_ptr:
std::shared_ptr<T> 指针名;
3.2 初始化方式
(1)用新对象初始化
std::shared_ptr<T> ptr(new T()); // 接管裸指针
std::shared_ptr<T> ptr = std::make_shared<T>(); // 推荐:一次性分配对象+控制块
(2)用已有指针初始化
std::shared_ptr<T> ptr(already_existing_pointer); // 接管裸指针
3.3 shared_ptr 的常用成员方法
| 方法 | 说明 |
|---|---|
reset() | 释放当前对所管对象的所有权,并将 shared_ptr 置为空。 |
use_count() | 返回当前共享同一对象的 shared_ptr 实例数量(引用计数)。 |
unique() | 判断是否只有一个 shared_ptr 拥有该对象(即 use_count() == 1)。 |
get() | 返回保存的裸指针。使用时需小心,切勿手动 delete。 |
swap(shr_ptr2) | 与另一个 shared_ptr 交换所管对象的所有权。 |
3.4 shared_prt 示例
示例1:
// C++ 程序演示 shared_ptr 的用法
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show() { cout << "A::show()" << endl; }
};
int main()
{
shared_ptr<A> p1(new A); // 创建共享指针并访问对象
cout << p1.get() << endl; // 打印被管理对象的地址
p1->show();
shared_ptr<A> p2(p1); // 创建新的共享指针,共享所有权
p2->show();
// 打印 p1 和 p2 的地址
cout << p1.get() << endl;
cout << p2.get() << endl;
// 返回指向同一被管理对象的 shared_ptr 对象数量
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
// 放弃 p1 对对象的所有权,指针变为 NULL
p1.reset();
cout << p1.get() << endl; // 这将打印 nullptr 或 0
cout << p2.use_count() << endl;
cout << p2.get() << endl;
//这些行演示了 p1 不再管理对象(get() 返回 nullptr),但 p2 仍然管理同一个对象,因此其引用计数为 1。
return 0;
}
0x1365c20
A::show()
A::show()
0x1365c20
0x1365c20
2
2
0
1
0x1365c20
示例2:
// C++ 程序演示 make_shared 的用法
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> shr_ptr1 = make_shared<int>(42); // 使用 std::make_shared 创建共享指针
shared_ptr<int> shr_ptr2 = make_shared<int>(24);
cout << "Value 1: " << *shr_ptr1 << endl; // 使用解引用操作符 (*) 访问值
cout << "Value 2: " << *shr_ptr2 << endl;
shared_ptr<int> shr_ptr3 = shr_ptr1; // 使用赋值运算符 (=) 共享所有权
// 检查共享指针 1 和共享指针 3 是否指向同一对象
if (shr_ptr1 == shr_ptr3) {
cout << "shared pointer 1 and shared pointer 3 " "point to the same object." << endl;
}
shr_ptr2.swap(shr_ptr3); // 交换共享指针 2 和共享指针 3 的内容
cout << "Value 2 (after swap): " << *shr_ptr2 << endl; // 检查交换后的值
cout << "Value 3 (after swap): " << *shr_ptr3 << endl;
// 使用逻辑运算符检查共享指针是否有效
if (shr_ptr1 && shr_ptr2) {
cout << "Both shared pointer 1 and shared pointer " "2 are valid." << endl;
}
// 重置共享指针
shr_ptr1.reset();
}
Value 1: 42
Value 2: 24
shared pointer 1 and shared pointer 3 point to the same object.
Value 2 (after swap): 42
Value 3 (after swap): 24
Both shared pointer 1 and shared pointer 2 are valid.
4. weak_ptr 非拥有式“弱”引用
weak_ptr 是一种不拥有对象的智能指针,它只提供对某个资源的“弱”引用。 与 shared_ptr 类似,但它不参与引用计数。
因此,它不会阻止对象被销毁,专门用来打破两个或多个对象互相用 shared_ptr 指向对方而形成的循环引用。
weak_ptr 是一种智能指针,相比裸指针风险更低。它可以指向由 shared_ptr 管理的资源,但不拥有该资源,即只建立“非拥有性”引用,不影响引用计数。
为什么需要 weak_ptr:
shared_ptr 的典型问题是循环引用:
若对象 A 用 shared_ptr 持有 B,对象 B 又用 shared_ptr 持有 A,双方引用计数永远不为 0,导致内存泄漏。
weak_ptr 用来打破这种循环:
把其中一条(或两条)持有关系改为 weak_ptr,对象就能正常释放,同时仍可安全地访问对方(需先提升成 shared_ptr)。
4.1 语法
std::weak_ptr<类型> 名字 ; 其中 `类型` 是它要指向的数据类型。
4.2 std::weak_ptr 常用成员函数
| 序号 | 函数 | 功能 |
|---|---|---|
| 1 | reset() | 清空当前 weak_ptr,不再引用任何资源。 |
| 2 | swap(weak_ptr2) | 与另一个 weak_ptr 交换所观察的对象。 |
| 3 | expired() | 检查所观察的资源是否已被释放(返回 true 表示资源不存在)。 |
| 4 | lock() | 若资源仍存在,返回一个持有该资源的 shared_ptr;否则返回空的 shared_ptr。 |
| 5 | use_count() | 返回当前有多少个 shared_ptr 仍在共享该资源。 |
4.3 weak_ptr 的主要用途
weak_ptr 的强大之处!它让你能够"观察"对象而不"拥有"对象。
- 防止循环引用
当对象 A 需要引用对象 B,但又不拥有 B 时,用weak_ptr可避免双方用shared_ptr互相引用造成的内存泄漏。 - 缓存系统
缓存往往需要临时保存对象引用,但又不能阻碍对象被销毁。weak_ptr正好提供“非拥有”的观察方式,对象不再被需要时可立即回收。
4.4 weak_ptr 示例
// C++ 程序演示 weak_ptr 的用法
#include <iostream>
#include <memory>
using namespace std;
// 声明一个虚拟对象
class Object {
public:
Object(int value) : data(value){
cout << "Object created with value: " << data << endl;
}
~Object(){
cout << "Object destroyed with value: " << data << endl;
}
int data;
};
int main() // 主
{
shared_ptr<Object> sharedObjectA = make_shared<Object>(42); // 创建拥有资源所有权的共享指针
weak_ptr<Object> weakObjectA = sharedObjectA; // 为之前创建的共享对象创建弱指针
// 使用 weak_ptr 访问对象
if (!weakObjectA.expired()) {
cout << "The value stored in sharedObjectA:" << (*weakObjectA.lock()).data << endl;
}
// 删除对象
sharedObjectA.reset();
cout << "End of the Program";
return 0;
}
二、普通指针存在的问题
- 内存泄漏:当程序重复分配内存但从未释放时就会发生这种情况。这会导致过度消耗内存,最终致使系统崩溃。
- 野指针:从未使用有效对象或地址进行初始化的指针称为野指针。
- 悬空指针:如果一个指针指向的内存已在程序中被提前释放,则该指针称为悬空指针。
三、指针与智能指针
指针和智能指针之间的区别如下:
| 指针 | 智能指针 |
|---|---|
| 指针是一个变量,它保存一个内存地址以及关于该内存位置的数据类型信息。指针是一个指向内存中某个东西的变量。 | 简而言之,智能指针是包装了指针的类,或称作用域指针。 |
| 当指针超出其作用域时,它不会被以任何形式销毁。 | 当智能指针超出其作用域时,它会自行销毁。 |
| 指针效率相对较低,因为它们不支持其他特性。 | 智能指针更高效,因为它们具有内存管理的附加特性。 |
| 它们非常依赖人工/手动管理。 | 它们在本质上是自动化/预编程的。 |