C++: 智能指针进阶

最后更新:2026-08-26

第45课我们学了移动语义。

现在,我们要深入智能指针——现代C++内存管理的核心工具。

用了智能指针,就再也不用担心内存泄漏了。


1. 智能指针概述

(1) 1.1 什么是智能指针?

智能指针管理动态内存的模板类,自动释放内存(RAII)。

三种智能指针:

智能指针 功能 适用场景
unique_ptr 独占所有权 唯一拥有对象
shared_ptr 共享所有权 多个地方需要访问同一对象
weak_ptr 弱引用 解决循环引用


2. unique_ptr

(1) 2.1 基本用法

unique_ptr独占所有权的智能指针,不能拷贝,只能移动。

示例:unique_ptr基本用法(难度⭐)

▶ 示例 2:代码示例(难度⭐)

CPP
#include <iostream>
#include <memory>

int main() {
 std::unique_ptr<int> p1(new int(42));
 std::cout << *p1 << std::endl; // 输出:42
 
 // std::unique_ptr<int> p2 = p1; // ❌ 错误!不能拷贝
 std::unique_ptr<int> p2 = std::move(p1); // ✅ 可以移动
 std::cout << *p2 << std::endl; // 输出:42
 
 return 0;
} // p2自动释放内存
▶ 试一试

输出:

TEXT 📖 仅展示
(程序输出)

(2) 2.2 自定义删除器

示例:用unique_ptr管理文件(难度⭐⭐)

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

// 自定义删除器:关闭文件
struct FileDeleter {
 void operator()(FILE* fp) const {
 if (fp) {
 fclose(fp);
 std::cout << "文件已关闭" << std::endl;
 }
 }
};

int main() {
 std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
 // 不需要手动fclose,unique_ptr自动调用FileDeleter
 
 return 0;
}


3. shared_ptr

(1) 3.1 基本用法

shared_ptr共享所有权的智能指针,用引用计数管理内存。

示例:shared_ptr基本用法(难度⭐)

CPP
#include <iostream>
#include <memory>

int main() {
 std::shared_ptr<int> p1 = std::make_shared<int>(42);
 std::cout << "引用计数:" << p1.use_count() << std::endl; // 1
 
 {
 std::shared_ptr<int> p2 = p1; // 拷贝,引用计数+1
 std::cout << "引用计数:" << p1.use_count() << std::endl; // 2
 } // p2销毁,引用计数-1
 
 std::cout << "引用计数:" << p1.use_count() << std::endl; // 1
 
 return 0;
}

(2) 3.2 make_shared vs new

推荐用法:std::make_shared 而不是 new

对比 new make_shared
异常安全 可能泄漏 安全
性能 两次分配 一次分配
代码简洁 冗长 简洁
CPP
// 推荐
auto p1 = std::make_shared<int>(42);

// 不推荐
std::shared_ptr<int> p2(new int(42));


4. weak_ptr

(1) 4.1 为什么需要weak_ptr?

问题: shared_ptr 可能导致循环引用,内存泄漏。

示例:循环引用(难度⭐⭐⭐)

CPP
#include <iostream>
#include <memory>

struct Node {
 std::shared_ptr<Node> next; // Circular reference!
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // Circular reference
 n2->next = n1;
 
 return 0;
} // ❌ n1 and n2 will not be released (memory leak)

(2) 4.2 用weak_ptr打破循环引用

解决方案: 把其中一个 shared_ptr 改为 weak_ptr

CPP
struct Node {
 std::weak_ptr<Node> next; // Use weak_ptr, does not increase reference count
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // Does not increase reference count
 n2->next = n1;
 
 return 0;
} // ✅ Released correctly


5. 智能指针选择指南

(1) 5.1 如何选择?

场景 推荐
唯一拥有 unique_ptr
共享拥有 shared_ptr
观察者 weak_ptr 或原始指针
数组 unique_ptr<T>

(2) 5.2 不要做的事

错误 说明
不要用多个原始指针初始化shared_ptr 会导致重复释放
不要混用原始指针和智能指针 破坏RAII
不要手动调用delete 让智能指针管理


6. 实战:智能指针管理资源

▶ 示例 1:用智能指针管理数据库连接(难度⭐⭐⭐)

CPP
#include <iostream>
#include <memory>

// 模拟数据库连接
class DatabaseConnection {
public:
 DatabaseConnection() {
 std::cout << "连接数据库" << std::endl;
 }
 
 ~DatabaseConnection() {
 std::cout << "断开连接" << std::endl;
 }
 
 void query(const std::string& sql) {
 std::cout << "执行SQL:" << sql << std::endl;
 }
};

int main() {
 // 用unique_ptr管理独占资源
 std::unique_ptrDatabaseConnection conn(new DatabaseConnection());
 conn->query("SELECT * FROM users");
 
 return 0;
} // 自动断开连接
▶ 试一试

输出:

TEXT 📖 仅展示
连接数据库
断开连接
执行SQL:

❓ 常见问题

Q unique_ptr和shared_ptr哪个快?
A unique_ptr 更快(无引用计数开销),优先用 unique_ptr

Q 什么时候用原始指针?
A - 不拥有所有权时(观察者) - 与C库交互时 - 性能极度敏感时(但通常不需要)

Q:shared_ptr线程安全吗? A:- 引用计数的修改是线程安全的 - 但指向的对象不是线程安全的(需要互斥量保护)


▶ 示例 3:shared_ptr共享所有权(难度⭐)

CPP
#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "构造" << std::endl; }
    ~Resource() { std::cout << "析构" << std::endl; }
};

int main() {
    {
        std::shared_ptr<Resource> p1 = std::make_shared<Resource>();
        std::shared_ptr<Resource> p2 = p1; // 共享所有权

        std::cout << "引用计数:" << p1.use_count() << std::endl;
    }
    // 离开作用域,自动析构

    std::cout << "程序结束" << std::endl;
    return 0;
}
▶ 试一试

输出:

TEXT 📖 仅展示
构造
析构
引用计数:
程序结束
💡 提示shared_ptr 共享所有权,引用计数归零时自动释放。用 make_shared 创建更安全。


知识点 要点
unique_ptr 独占所有权,不能拷贝
shared_ptr 共享所有权,引用计数
weak_ptr 弱引用,打破循环引用
make_shared 推荐用法,异常安全
选择原则 优先unique_ptr,需要时再用shared_ptr

📖 小节

📝 作业

  1. **基础题 (Difficulty ⭐):用 unique_ptr 管理一个动态分配的 int,用 make_unique 创建。尝试拷贝 unique_ptr(应该编译失败),改用 move。

  2. **进阶题 (Difficulty ⭐⭐):用 shared_ptr 实现多个对象共享同一个资源。创建两个 shared_ptr 指向同一个对象,输出 use_count() 观察引用计数变化。

  3. **挑战题 (Difficulty ⭐⭐⭐):用 weak_ptr 解决 shared_ptr 循环引用问题。创建 A 类和 B 类互相持有 shared_ptr,观察内存泄漏。改用 weak_ptr 后验证正确释放。



下一课:模板元编程入门(#47)

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏