一、构造函数中的资源管理:从裸指针到智能指针
构造函数里管理动态资源,这个话题我聊过很多次。说白了,就是「谁分配,谁释放」的问题。但实际项目中,这个原则执行起来并不容易。
我记得刚工作那会儿,接手过一个老项目。代码里到处都是 new 和 delete,构造函数里 new 一堆东西,析构函数里 delete 一堆东西。看起来挺对称,对吧?但问题是——如果构造函数中途抛异常了,那些已经 new 出来的资源谁来释放?
嗯,这就是经典的「构造函数异常安全」问题。
1.1 裸指针的痛点
先看一个反面教材:
class Widget {
Resource* res1_;
Resource* res2_;
public:
Widget() {
res1_ = new Resource(); // 假设这里成功
res2_ = new Resource(); // 如果这里抛异常,res1_ 就泄漏了
}
~Widget() {
delete res1_;
delete res2_;
}
};
为什么会这样?因为 C++ 的析构函数只有在对象构造完全成功后才会被调用。如果构造函数中途抛异常,对象就没活下来,析构函数自然也不会执行。
1.2 智能指针的救场
解决方案其实很简单——用智能指针代替裸指针。智能指针的析构函数会在异常发生时自动执行,释放已持有的资源。
class Widget {
std::unique_ptr<Resource> res1_;
std::unique_ptr<Resource> res2_;
public:
Widget()
: res1_(std::make_unique<Resource>())
, res2_(std::make_unique<Resource>()) // 如果这里抛异常,res1_ 自动释放
{}
// 不需要自定义析构函数!
};
我个人习惯是:只要涉及动态资源,优先用 unique_ptr。它零开销,而且异常安全。你想想看,连析构函数都不用写了,多省心。
二、shared_ptr 与 shared_from_this
讲完 unique_ptr,咱们聊聊 shared_ptr。这东西在项目中用得也很多,但有个坑特别容易踩——在类内部获取指向自身的 shared_ptr。
2.1 问题场景
假设你有一个类,它需要把自己传给某个回调函数或者异步任务:
class Task {
public:
void start() {
// 启动一个异步任务,需要传递 this
async_execute([this]() {
this->doWork(); // 危险!this 可能已经失效
});
}
void doWork() { /* ... */ }
};
这段代码的问题在于:异步任务执行时,this 指向的对象可能已经被销毁了。这就是经典的「悬垂指针」问题。
你可能会想:「那我传 shared_ptr<Task> 不就行了?」但问题来了——在成员函数内部,你拿不到指向自己的 shared_ptr。
2.2 enable_shared_from_this 登场
这就是 enable_shared_from_this 的用武之地。它让一个类能够安全地生成指向自身的 shared_ptr。
class Task : public std::enable_shared_from_this<Task> {
public:
void start() {
auto self = shared_from_this(); // 获取指向自身的 shared_ptr
async_execute([self]() {
self->doWork(); // 安全!self 会保持对象存活
});
}
void doWork() { /* ... */ }
};
shared_from_this() 的前提是——对象必须已经被某个 shared_ptr 管理。如果对象是栈上分配的,或者用裸指针 new 出来的,调用 shared_from_this() 会抛出 std::bad_weak_ptr 异常。
2.3 使用注意事项
我曾经在项目中踩过这个坑。有个同事在构造函数里调用了 shared_from_this(),结果程序直接崩了。原因很简单——构造函数执行时,对象还没被 shared_ptr 接管。
正确的做法是:
- 不要在构造函数中调用
shared_from_this() - 不要在析构函数中调用
shared_from_this() - 确保对象已经被
shared_ptr管理后再调用
shared_from_this(),可以考虑使用工厂模式,强制用户通过 shared_ptr 创建对象。
三、知识体系总览
下面这张图总结了本章的核心知识点和它们之间的关系:
四、最佳实践总结
聊了这么多,最后总结几条我个人的经验:
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 构造函数中管理资源 | 使用 unique_ptr 或 shared_ptr |
异常安全,自动释放 |
类内部需要获取自身 shared_ptr |
继承 enable_shared_from_this |
避免悬垂指针 |
| 需要自定义析构行为 | 使用智能指针的删除器 | 保持 RAII 风格 |
| 对象生命周期需要共享管理 | 使用 shared_ptr |
引用计数自动管理 |
unique_ptr。只有在确实需要共享所有权时,才换成 shared_ptr。至于 enable_shared_from_this,它是个好工具,但别忘了它的使用前提——对象必须已经被 shared_ptr 管理。
嗯,关于构造函数和智能指针的配合,今天就聊到这儿。记住一句话:智能指针不是银弹,但它能帮你解决 90% 的资源管理问题。剩下的 10%,靠的是你对 RAII 和异常安全的理解深度。