一、构造函数中的资源管理:从裸指针到智能指针

构造函数里管理动态资源,这个话题我聊过很多次。说白了,就是「谁分配,谁释放」的问题。但实际项目中,这个原则执行起来并不容易。

我记得刚工作那会儿,接手过一个老项目。代码里到处都是 newdelete,构造函数里 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++ 的析构函数只有在对象构造完全成功后才会被调用。如果构造函数中途抛异常,对象就没活下来,析构函数自然也不会执行。

注意:构造函数中如果使用裸指针管理资源,一旦中途抛出异常,已分配的资源将无法被自动释放。这是 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_from_this 解决悬垂指针问题 必须由 shared_ptr 管理 核心原则:谁分配,谁负责;异常安全优先

四、最佳实践总结

聊了这么多,最后总结几条我个人的经验:

场景 推荐做法 原因
构造函数中管理资源 使用 unique_ptrshared_ptr 异常安全,自动释放
类内部需要获取自身 shared_ptr 继承 enable_shared_from_this 避免悬垂指针
需要自定义析构行为 使用智能指针的删除器 保持 RAII 风格
对象生命周期需要共享管理 使用 shared_ptr 引用计数自动管理
我的建议:写新代码时,默认用 unique_ptr。只有在确实需要共享所有权时,才换成 shared_ptr。至于 enable_shared_from_this,它是个好工具,但别忘了它的使用前提——对象必须已经被 shared_ptr 管理。

嗯,关于构造函数和智能指针的配合,今天就聊到这儿。记住一句话:智能指针不是银弹,但它能帮你解决 90% 的资源管理问题。剩下的 10%,靠的是你对 RAII 和异常安全的理解深度。

公众号:蓝海资料掘金营,微信 deep3321