4.2、std::unique_ptr/std::shared_ptr/std::weak_ptr(智能指针)

智能指针的设计哲学与核心价值

智能指针的设计基于RAII(资源获取即初始化)原则,核心目标是:

  • • 自动管理资源生命周期,避免手动delete带来的错误。
  • • 明确资源所有权,通过不同智能指针表达不同的所有权语义。
  • • 异常安全,保证异常发生时资源正确释放。
  • • 解决循环引用,通过weak_ptr打破shared_ptr循环依赖。

智能指针不仅是内存管理工具,更是现代C++设计理念的体现,让资源管理成为语言级别的安全机制。

1. 新特性使用方法:基础示例与底层细节

std::unique_ptr -- 独占所有权,轻量高效

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void say() { std::cout << "Hello from Resource\n"; }
};

void uniquePtrDemo() {
    std::unique_ptr<Resource> ptr = std::make_unique<Resource>();
    ptr->say();
    // ptr超出作用域时,自动调用Resource析构函数释放资源
}

int main() {
    uniquePtrDemo();
    return 0;
}

底层原理:unique_ptr封装了裸指针,禁止复制构造和赋值,只支持移动语义。它的大小与裸指针相同,重载了operator*operator->,使用时几乎无性能开销。资源释放由析构函数自动完成,保证异常安全。

std::shared_ptr -- 共享所有权,引用计数管理

#include <iostream>
#include <memory>

void sharedPtrDemo() {
    auto sp1 = std::make_shared<Resource>();
    {
        std::shared_ptr<Resource> sp2 = sp1; // 引用计数+1
        std::cout << "Use count inside block: " << sp1.use_count() << std::endl;
        sp2->say();
    } // sp2销毁,引用计数-1
    std::cout << "Use count after block: " << sp1.use_count() << std::endl;
}

int main() {
    sharedPtrDemo();
    return 0;
}

底层原理:shared_ptr内部维护一个引用计数控制块,所有指向同一资源的shared_ptr共享该计数。计数通过原子操作保证线程安全。最后一个shared_ptr销毁时释放资源和控制块。

std::weak_ptr -- 弱引用,解决循环引用

#include <iostream>
#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev; // 弱引用,避免循环引用
    ~Node() { std::cout << "Node destroyed\n"; }
};

void weakPtrDemo() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();
    n1->next = n2;
    n2->prev = n1; // weak_ptr不增加引用计数,打破循环引用
}

int main() {
    weakPtrDemo();
    return 0;
}

底层原理:weak_ptr不拥有资源,不增加引用计数。它持有一个指向shared_ptr控制块的弱引用,可以检测资源是否仍存在。通过lock()尝试获取shared_ptr,安全访问资源,避免悬空指针。

2. 进阶用法:自定义删除器与enable_shared_from_this

自定义删除器

#include <iostream>
#include <memory>
#include <cstdio>

struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) {
            std::cout << "Closing file\n";
            fclose(fp);
        }
    }
};

void customDeleterDemo() {
    std::unique_ptr<FILE, FileCloser> filePtr(fopen("test.txt""w"));
    if (filePtr) {
        fputs("Hello, world!", filePtr.get());
    }
} // filePtr销毁时自动调用FileCloser释放资源

int main() {
    customDeleterDemo();
    return 0;
}

enable_shared_from_this示例

#include <iostream>
#include <memory>

class Widget : public std::enable_shared_from_this<Widget> {
public:
    std::shared_ptr<Widget> getPtr() {
        return shared_from_this();
    }
    void say() { std::cout << "Widget here\n"; }
};

void sharedFromThisDemo() {
    auto w = std::make_shared<Widget>();
    auto w2 = w->getPtr();
    w2->say();
    std::cout << "Use count: " << w.use_count() << std::endl;
}

int main() {
    sharedFromThisDemo();
    return 0;
}

3. 常见错误使用及其后果

  • • 裸指针与智能指针混用:导致重复释放或内存泄漏。
  • • 复制unique_ptr:编译错误,必须用std::move转移所有权。
  • • 循环引用:shared_ptr互相持有,导致内存无法释放,必须用weak_ptr打破。
  • • 错误访问已过期的weak_ptr:未检测资源是否存在,导致悬空访问。
  • • 用裸指针初始化多个shared_ptr:导致多重析构崩溃。
  • • 过度使用shared_ptr:引用计数开销影响性能。

大项目中智能指针的实践策略

  • • 优先使用unique_ptr管理独占资源,如工厂函数返回值、局部资源管理,性能最佳且语义明确。
  • • 共享资源时使用shared_ptr,但需谨慎,避免滥用。
  • • 设计双向关系或缓存时,用weak_ptr防止循环引用。
  • • 接口设计时明确智能指针传递方式,避免不必要的引用计数增加。
  • • 统一使用std::make_uniquestd::make_shared创建智能指针,提高异常安全和性能。
  • • 代码审查重点关注智能指针用法,防止裸指针与智能指针混用。

总结

智能指针不仅是内存管理的工具,更是现代C++设计哲学的体现:自动化资源管理、明确所有权语义、提升异常安全性。它们让C++代码更健壮、更易维护,同时保留了高性能和灵活性。

真正掌握智能指针,意味着你能从繁琐且易错的手动内存管理中解放出来,把精力放在业务逻辑和算法创新上。智能指针是现代C++程序员不可或缺的武器,是写出高质量代码的关键。
本文首发于【讳疾忌医-note】公众号,未经授权,不得转载。
(加入我的知识星球,免费获取账号,解锁所有文章。)

 

阅读剩余
THE END