C++ 智能指针是现代 C++ 编程中用于管理动态内存和资源的核心工具。它们基于 RAII(Resource Acquisition Is Initialization,获取资源即初始化)惯用法,确保在对象生命周期结束时自动释放资源,从而有效防止内存泄漏并提高程序的异常安全性。
1. 核心概念与优势
- 自动内存管理:智能指针是栈上分配的类模板对象,它们封装了堆上分配的原始指针。当智能指针超出作用域时,其析构函数会自动被调用,进而释放所管理的内存或资源。
- 异常安全:即使代码执行过程中抛出异常,栈展开机制也会确保智能指针的析构函数被执行,从而避免资源泄漏。
- 替代原始指针 : 在现代 C++ 中,应尽量避免直接使用
new和delete。原始指针仅应用于范围极小、性能极度敏感且不涉及所有权转移的场景。
2. 主要类型
C++ 标准库(<memory> 头文件)提供了三种主要的智能指针,分别适用于不同的所有权场景:
(1) std::unique_ptr ------ 独占所有权
(2) std::shared_ptr ------ 共享所有权
(3) std::weak_ptr ------ 弱引用(观察权)
cpp
#include <iostream>
#include <memory>
int main()
{
/* 1、******************** unique_ptr *************** */
//独占所有权
std::unique_ptr<int> ptr = std::make_unique<int>(12);
std::cout << "ptr value: " << *ptr << std::endl;
//转移所有权,此时ptr为空,ptr2拥有资源
//ptr2接管了堆上int对象的所有权,ptr被重置为nullptr(空指针),它不再管理任何资源
std::unique_ptr<int> ptr2 = std::move(ptr);
//*ptr: 解引用空指针,程序崩溃
//ptr->method(); 通过空指针调用成员函数,程序崩溃
//可以判断状态 if(ptr) if(ptr == nullptr)
//重新赋值 ptr = std::make_unique<int>(100);
if ( ptr == nullptr )
{
std::cout << "ptr is now null" << std::endl;
}
if (ptr2)
{
std::cout << "ptr2 value: " << *ptr2 << std::endl;
}
/* 2、******************** shared_ptr *************** */
//共享所有权
//引用计数:内部维护一个引用计数(use_count).每增加一个指向同一对象的shared_ptr,计数加1;每销毁一个计数减1
//自动释放:当引用计数降为0时,自动删除所管理的对象
//线程安全:引用计数的增减是原子操作,因此多个线程可以安全地拷贝或销毁指向同一个对象的shared_ptr
//注意:这并不意味着所管理的对象本身是线程安全的
//适用场景:多个所有者需要共享同一资源,且无法确定哪个所有者最后销毁资源
//推荐使用std::make_shared<T>(args...) 相比直接new,make_shared只需一次内存分配(同时分配控制块和对象)
std::shared_ptr<int> shptr = std::make_shared<int>(22);
std::shared_ptr<int> shptr2 = shptr; //共享所有权,引用计数变成2
std::cout << "use count: " << shptr.use_count() << std::endl; //输出2
shptr2.reset(); //ptr2 释放所有权,引用计数变为 1
std::cout << "use count after reset: " << shptr.use_count() << std::endl; //输出1
/* 3、******************** weak_ptr *************** */
//弱引用,观察权
//不拥有资源:weak_ptr 不会增加引用计数,因此不会影响对象的生命周期
//临时访问:必须通过.lock()方法尝试提升为shared_ptr才能访问对象。如果对象已被销毁,lock()返回空的shared_ptr
//适用场景:解决循环引用,当两个对象互相持有对方的shared_ptr时,引用计数永远无法归零,导致内存泄露。
//将其中一方改为weak_ptr可打破循环
//缓存或观察者模式:需要访问对象但不希望阻止其被销毁
return 0;
}
3. 控制块(Control Block)
std::shared_ptr对象本身并不直接包含引用计数变量。相反、它内部通常包含两个指针(在64位系统上各占8字节)
资源指针(Resource Pointer): 指向实际管理的对象
控制块指针(Control Block Pointer):指向一个动态分配的控制块。
注意:std::shared_ptr的大小通常时原始指针的两倍(因为存了两个指针),而引用计数的操作需要通过控制块指针间接访问。
对象销毁:当强引用计数(strong_count)变为0时,控制块中存储的删除器会被调用,从而销毁实际管理的对象。
控制块销毁:只有当强引用计数和弱引用计数(weak_count)同时变为0时,控制块本身的内存才会被释放。
这就是为什么即使所有shared_ptr都销毁了,如果还存在weak_ptr,实际对象的内存虽然被释放了,但控制块的内存依然保留,以便weak_ptr能安全地查询对象是否存活。
std::shared_ptr 的引用计数操作(增加和减少)使用的是原子操作 (Atomic Operations)
这意味着多个线程可以同时拷贝或销毁指向同一对象的 shared_ptr,而不会导致数据竞争或引用计数错误。
重要限制 :原子性仅保证引用计数本身 的安全,不保证 所指向对象的数据访问安全。如果多个线程通过 shared_ptr 读写同一个对象的内容,仍需使用互斥锁 (Mutex) 等同步机制。