C++11新特性--智能指针
C++11新特性--智能指针

1. 为什么需要智能指针?

在传统 C++ 里,动态分配内存需要手动 newdelete。比如:

1int* p = new int(10);
2// 使用 p
3delete p; // 必须记得 delete

问题:

  • 忘记 delete内存泄漏

  • 重复 delete程序崩溃

  • 提前 delete悬空指针(指针还在,但指向的内存已经无效)。

智能指针就是为了解决这些痛点的。


2. C++11 提供了哪些智能指针?

智能指针说明
std::unique_ptr独占式智能指针,一个对象只能有一个 unique_ptr 拥有。不能复制,只能移动。
std::shared_ptr共享式智能指针,多个 shared_ptr 可以共同拥有一个对象。内部有引用计数。
std::weak_ptr弱引用智能指针,不拥有对象,只是观察。配合 shared_ptr 使用,防止循环引用。

3. 各种智能指针的详细讲解

3.1 std::unique_ptr

特点

  • 资源独占,不能复制,只能移动。

  • 析构时自动 delete 管理的对象。

示例

 1#include <memory>
 2#include <iostream>
 3
 4int main() {
 5    std::unique_ptr<int> uptr(new int(42));
 6    std::cout << *uptr << std::endl;
 7
 8    // std::unique_ptr<int> uptr2 = uptr; // 错误,不能复制
 9    std::unique_ptr<int> uptr2 = std::move(uptr); // 可以移动
10
11    if (!uptr) {
12        std::cout << "uptr为空了" << std::endl;
13    }
14}

注意

  • std::move 转移所有权。

  • 推荐用 std::make_unique(C++14 加的)创建:

1auto uptr = std::make_unique<int>(42);

3.2 std::shared_ptr

特点

  • 多个 shared_ptr 可以共同拥有一个对象。

  • 内部有一个引用计数器,记录有多少个 shared_ptr 指向这个对象。

  • 最后一个 shared_ptr 销毁时,才释放资源。

示例

1#include <memory>
2#include <iostream>
3
4int main() {
5    std::shared_ptr<int> sptr1 = std::make_shared<int>(100);
6    std::shared_ptr<int> sptr2 = sptr1; // 引用计数 +1
7
8    std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出 2
9}

注意

  • make_shared效率高,推荐用。

  • 多个 shared_ptr 指向同一资源没问题。

内部原理

  • shared_ptr 内部有一块小内存,记录引用计数

  • 每次拷贝 +1,销毁 -1,归零就 delete

3.3 std::weak_ptr

特点

  • weak_ptr 不影响引用计数。

  • 主要用于观察 shared_ptr 管理的对象防止循环引用

示例

 1#include <memory>
 2#include <iostream>
 3
 4int main() {
 5    std::shared_ptr<int> sptr = std::make_shared<int>(200);
 6    std::weak_ptr<int> wptr = sptr;
 7
 8    std::cout << "引用计数: " << sptr.use_count() << std::endl; // 1,不变
 9
10    if (auto spt = wptr.lock()) { // 尝试拿到 shared_ptr
11        std::cout << *spt << std::endl;
12    } else {
13        std::cout << "对象已经被释放" << std::endl;
14    }
15}

lock() 方法

  • wptr.lock() 返回一个 shared_ptr

  • 如果资源还活着,就能成功;否则返回 nullptr


4. 三者的关系小总结

  • unique_ptr:独占。不能拷贝,只能转移。

  • shared_ptr:共享。内部引用计数。

  • weak_ptr:观察,不拥有,不增加计数。


5. 使用智能指针的注意事项

✅ 推荐用 make_sharedmake_unique 创建对象,效率高又安全。

✅ 注意避免循环引用(两个对象相互持有 shared_ptr → 资源释放不了,要用 weak_ptr 打破环)。

✅ 不要手动 delete 智能指针管理的对象(会出大问题)。

✅ 小心从原始指针(T*)转换成智能指针时的所有权问题。


6. 小例子:智能指针管理类对象

 1#include <memory>
 2#include <iostream>
 3
 4class Test {
 5public:
 6    Test() { std::cout << "Test constructed" << std::endl; }
 7    ~Test() { std::cout << "Test destructed" << std::endl; }
 8};
 9
10int main() {
11    {
12        auto ptr = std::make_shared<Test>();
13    } // 离开作用域,自动析构
14    std::cout << "End of main" << std::endl;
15}

输出:

Test constructed
Test destructed
End of main

智能指针帮你自动管理生命周期,不用手动 delete,非常优雅!


7. 智能指针的底层机制(稍微技术一点)

7.1 shared_ptr 的内部结构

可以理解成它维护了两样东西:

  • 资源指针:真正指向管理对象的指针。

  • 控制块(Control Block):保存引用计数、弱引用计数等信息。

大概结构像这样(画个简图给你看):

+----------------+         +-----------------+
| shared_ptr A   |         |  Control Block   |
| -------------  | ----->  | - strong_count=2 |
| - ptr          |         | - weak_count=1   |
+----------------+         +-----------------+
       |
+----------------+
|   Managed Obj  |
+----------------+
  • strong_count:多少个 shared_ptr 正在管理对象。

  • weak_count:多少个 weak_ptr 观察这个对象。

strong_count == 0

  • 资源对象释放。

  • 但控制块还保留(因为可能还有 weak_ptr)。

strong_count == 0 && weak_count == 0

  • 控制块也释放。

7.2 unique_ptr 的内部结构

超级简单:

  • 只有一个裸指针。

  • 也没有控制块,也没有引用计数。

就是一个单纯的拥有者,生命周期清清爽爽。


8. 智能指针的自定义删除器

有时候,delete 不是你想要的销毁方式,比如释放文件句柄、释放套接字,就可以用自定义删除器。

8.1 unique_ptr 自定义删除器

 1#include <memory>
 2#include <iostream>
 3
 4struct FileCloser {
 5    void operator()(FILE* fp) const {
 6        if (fp) {
 7            fclose(fp);
 8            std::cout << "File closed" << std::endl;
 9        }
10    }
11};
12
13int main() {
14    std::unique_ptr<FILE, FileCloser> fp(fopen("test.txt", "w"));
15    if (fp) {
16        fputs("Hello, World!", fp.get());
17    }
18}
  • 这里 FileCloser 是一个仿函数,专门干**fclose**工作。

  • unique_ptr 可以带一个删除器类型参数。

8.2 shared_ptr 自定义删除器

 1#include <memory>
 2#include <iostream>
 3
 4int main() {
 5    auto deleter = [](int* p) {
 6        std::cout << "Deleting int: " << *p << std::endl;
 7        delete p;
 8    };
 9
10    std::shared_ptr<int> sptr(new int(10), deleter);
11}
  • shared_ptr 也支持自定义删除器,常用 lambda 表达式写。

9. 小心智能指针的坑

9.1 不要多次管理同一个裸指针!

错误示例

1int* p = new int(5);
2std::shared_ptr<int> sp1(p);
3std::shared_ptr<int> sp2(p); // ❌ 两个shared_ptr都以为自己该负责delete!
  • 结果:程序崩溃,双重释放。

✅ 正确做法是,只交给一个智能指针管理:

1auto sp1 = std::make_shared<int>(5);
2// 需要共享,用拷贝sp1即可。
3auto sp2 = sp1;

10. 总结大图!

我画了一张总结图,来帮你快速记忆:

【智能指针总结】

┌──────────────┐
│ unique_ptr   │
│ - 独占资源    │
│ - 不可拷贝    │
│ - 可移动      │
│ - 轻量快      │
└──────────────┘
        ↓  转移所有权 (move)
┌──────────────┐
│ shared_ptr   │
│ - 共享资源    │
│ - 引用计数    │
│ - 自动释放    │
└──────────────┘
        ↓  观察对象
┌──────────────┐
│ weak_ptr     │
│ - 不拥有资源  │
│ - 不增加计数  │
│ - 防止循环引用│
└──────────────┘

11. 小扩展:什么时候用哪种智能指针?

场景推荐指针
资源归属唯一,简单的对象生命周期unique_ptr
资源需要多个对象共享shared_ptr
需要观察但不管理资源,防止环状引用weak_ptr

实际开发中

  • 能用 unique_ptr 就用 unique_ptr,开销最小。

  • 只有确实需要共享所有权时才用 shared_ptr

  • 避免循环引用时配合 weak_ptr



🎯 案例一:unique_ptr 管理动态资源

场景:管理单个对象,保证无拷贝,自动释放。

 1#include <iostream>
 2#include <memory>
 3
 4class Widget {
 5public:
 6    Widget() { std::cout << "Widget created\n"; }
 7    ~Widget() { std::cout << "Widget destroyed\n"; }
 8    void operation() { std::cout << "Widget operation\n"; }
 9};
10
11void useWidget() {
12    std::unique_ptr<Widget> w = std::make_unique<Widget>();
13    w->operation(); // 使用对象
14} // 离开作用域,自动销毁 Widget
15
16int main() {
17    useWidget();
18    return 0;
19}

要点

  • 使用 std::make_unique 创建对象。

  • 不需要手动 delete,更安全。


🎯 案例二:shared_ptr 多个共享者共同管理对象

场景:多个模块或组件需要共享访问同一资源

 1#include <iostream>
 2#include <memory>
 3
 4class Data {
 5public:
 6    Data(int v) : value(v) { std::cout << "Data created\n"; }
 7    ~Data() { std::cout << "Data destroyed\n"; }
 8    void show() const { std::cout << "Value: " << value << "\n"; }
 9private:
10    int value;
11};
12
13void processData(std::shared_ptr<Data> p) {
14    p->show();
15}
16
17int main() {
18    std::shared_ptr<Data> data = std::make_shared<Data>(42);
19    processData(data);
20    processData(data);
21
22    std::cout << "Use count: " << data.use_count() << "\n"; // 1
23    return 0;
24}

要点

  • shared_ptr 的引用计数自动管理资源。

  • 可以安全地跨函数、跨模块传递。


🎯 案例三:用 weak_ptr 避免循环引用

场景:两个对象互相持有指针,容易形成循环引用。

 1#include <iostream>
 2#include <memory>
 3
 4class B; // 前向声明
 5
 6class A {
 7public:
 8    std::shared_ptr<B> b_ptr;
 9    ~A() { std::cout << "A destroyed\n"; }
10};
11
12class B {
13public:
14    std::weak_ptr<A> a_ptr; // 用 weak_ptr 破坏循环引用
15    ~B() { std::cout << "B destroyed\n"; }
16};
17
18int main() {
19    auto a = std::make_shared<A>();
20    auto b = std::make_shared<B>();
21    a->b_ptr = b;
22    b->a_ptr = a;
23
24    // 两者离开作用域时能正确释放
25    return 0;
26}

如果用 shared_ptr 互相持有,就会造成内存泄漏!
weak_ptr 打破强引用链,可以正常释放。


🎯 案例四:自定义删除器 shared_ptr 管理非堆内存

场景:管理资源,比如文件描述符、网络连接等,不能直接 delete

 1#include <iostream>
 2#include <memory>
 3#include <cstdio> // C文件操作
 4
 5void customDeleter(FILE* fp) {
 6    if (fp) {
 7        std::cout << "Closing file\n";
 8        fclose(fp);
 9    }
10}
11
12int main() {
13    std::shared_ptr<FILE> file(
14        fopen("test.txt", "w"), customDeleter);
15
16    if (file) {
17        fputs("Hello, world!\n", file.get());
18    }
19    // 文件会在 shared_ptr 销毁时自动关闭
20    return 0;
21}

要点

  • shared_ptr 支持自定义删除器。

  • 可以管理非 new 分配的资源。


🎯 案例五:智能指针搭配容器使用

场景:存储一组对象,自动管理生命周期。

 1#include <iostream>
 2#include <vector>
 3#include <memory>
 4
 5class Item {
 6public:
 7    Item(int id) : id(id) { std::cout << "Item " << id << " created\n"; }
 8    ~Item() { std::cout << "Item " << id << " destroyed\n"; }
 9    void show() const { std::cout << "Item ID: " << id << "\n"; }
10private:
11    int id;
12};
13
14int main() {
15    std::vector<std::unique_ptr<Item>> items;
16    items.push_back(std::make_unique<Item>(1));
17    items.push_back(std::make_unique<Item>(2));
18    items.push_back(std::make_unique<Item>(3));
19
20    for (const auto& item : items) {
21        item->show();
22    }
23    // 离开作用域,所有 Item 自动销毁
24    return 0;
25}

要点

  • unique_ptr 不能拷贝,但可以移动,因此可以存到 std::vector 中。

  • 避免忘记手动 delete,特别适合资源密集型应用。


🎯 总结一下这些案例的核心套路:

场景选择的智能指针备注
单独管理一个对象unique_ptr独占资源,支持移动
多人共享资源shared_ptr自动引用计数
观察共享对象,不持有weak_ptr防止循环引用
管理非堆内存资源shared_ptr + 自定义删除器如文件、socket
容器中管理对象vector<unique_ptr<T>>元素安全释放


最后修改于 2025-04-27 18:22