智能指针的应用
一. 基本概念
智能指针是 C++ 中用于管理动态内存的强大工具,旨在避免手动内存管理带来的内存泄漏和悬空指针问题。它们通过 RAII(资源获取即初始化)技术自动释放不再使用的内存。
核心概念
1. RAII(Resource Acquisition Is Initialization)
资源在对象构造时获取,在对象析构时释放。智能指针通过这一原则,将内存管理与对象生命周期绑定。
2. 所有权
智能指针通过控制对象的所有权来决定何时释放内存:
(1):独占所有权:同一时间只有一个指针可访问对象(如 std::unique_ptr
)。
(2):共享所有权:多个指针可共享对象,使用引用计数跟踪引用数(如 std::shared_ptr
)。
二. 智能指针的优点
1. 避免内存泄漏
问题:手动管理内存时,若忘记调用 delete
(如异常抛出或提前返回),会导致内存泄漏。
智能指针解决方案:对象生命周期结束时(如离开作用域),智能指针自动释放内存。
void manual_memory_leak() {
int* ptr = new int(42);
if (condition) return; // 提前返回,内存未释放
delete ptr; // 不会执行
}
void smart_ptr_safe() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
if (condition) return; // ptr 自动释放内存
} // 正常退出时,ptr 也会释放内存
2. 防止悬空指针
问题:多个指针指向同一对象,其中一个删除对象后,其他指针变为悬空指针。
智能指针解决方案:
1. std::unique_ptr
禁止拷贝,确保单一所有权。
2. std::shared_ptr
使用引用计数,所有指针销毁后才释放对象。
void dangling_ptr() {
int* ptr1 = new int(42);
int* ptr2 = ptr1;
delete ptr1;
*ptr2; // 悬空指针!
}
void smart_ptr_safe() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // 引用计数+1
ptr1.reset(); // 引用计数-1,对象仍存在
*ptr2; // 安全!
} // ptr2 销毁时,引用计数为0,对象被删除
3. 解决循环引用问题
问题:两个 shared_ptr
相互引用会导致引用计数永远不为 0,造成内存泄漏。
智能指针解决方案:使用 std::weak_ptr
打破循环引用。weak_ptr
不增加引用计数,仅观察对象。
struct Parent;
struct Child {
std::shared_ptr<Parent> parent; // 正常使用 shared_ptr
~Child() { std::cout << "Child destroyed" << std::endl; }
};
struct Parent {
std::weak_ptr<Child> child; // 使用 weak_ptr 避免循环引用
~Parent() { std::cout << "Parent destroyed" << std::endl; }
};
void circular_reference() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child = child; // weak_ptr 不增加引用计数
child->parent = parent; // shared_ptr 正常赋值
} // parent 和 child 均正常释放
三. C++ 标准库中的智能指针
C++ 提供了三种主要的智能指针,位于 <memory>
头文件中:
1. std::unique_ptr
特性:独占所有权,同一时间只能有一个指针指向对象。
用途:管理不共享的资源。
示例:
#include <memory>
void example() {
// 创建 unique_ptr
std::unique_ptr<int> ptr = std::make_unique<int>(42); // C++14
// 或使用 new(不推荐):std::unique_ptr<int> ptr(new int(42));
// 访问对象
*ptr = 100;
// 转移所有权(使用 std::move)
std::unique_ptr<int> ptr2 = std::move(ptr); // ptr 现在为空
// 不能复制 unique_ptr
// std::unique_ptr<int> ptr3 = ptr2; // 错误!
} // ptr2 离开作用域,自动释放内存
2. std::shared_ptr
特性:共享所有权,使用引用计数跟踪有多少指针指向同一对象。
用途:多个指针需要共享同一资源时。
示例:
#include <memory>
#include <iostream>
struct MyClass {
~MyClass() { std::cout << "Destroying MyClass" << std::endl; }
};
void example() {
// 创建 shared_ptr
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); // 引用计数 = 1
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数 = 2
std::cout << "Inside scope: " << ptr1.use_count() << std::endl; // 输出 2
} // ptr2 销毁,引用计数 = 1
std::cout << "Outside scope: " << ptr1.use_count() << std::endl; // 输出 1
} // ptr1 销毁,引用计数 = 0,对象被删除
3. std::weak_ptr
特性:弱引用,不控制对象生命周期,解决 shared_ptr
的循环引用问题。
用途:观察对象但不拥有它,避免循环引用导致的内存泄漏。
示例:
#include <memory>
struct B; // 前向声明
struct A {
std::weak_ptr<B> b_ptr; // 使用 weak_ptr 避免循环引用
~A() { std::cout << "A destroyed" << std::endl; }
};
struct B {
std::shared_ptr<A> a_ptr; // 正常使用 shared_ptr
~B() { std::cout << "B destroyed" << std::endl; }
};
void example() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b; // weak_ptr 可直接赋值
b->a_ptr = a; // shared_ptr 正常赋值
// 使用 weak_ptr 前需检查对象是否存在
if (auto shared_b = a->b_ptr.lock()) { // 创建临时 shared_ptr
// 使用 shared_b 访问 B 对象
}
} // a 和 b 都能正常释放,无内存泄漏
四. 最佳实践
1. 优先使用 std::unique_ptr
:当资源不需要共享时。
2. 使用 std::make_unique
和 std::make_shared
:更安全地创建智能指针(异常安全)。
3. 避免混合使用智能指针和原始指针:
int* raw = new int(42);
std::shared_ptr<int> ptr1(raw); // 危险!
std::shared_ptr<int> ptr2(raw); // 错误!多个独立的 shared_ptr 管理同一内存
4. 使用std::weak_ptr打破循环引用
5. 避免从 this
创建 shared_ptr
:使用 std::enable_shared_from_this
安全获取 shared_ptr
。