《 C++ 零基础入门教程》第5章:智能指针与 RAII —— 让内存管理自动化

✅ 本篇目标:

  • 理解 RAII(资源获取即初始化)思想
  • 掌握 unique_ptrshared_ptr
  • 学会用智能指针管理动态对象
  • 实战:用智能指针重构通讯录,支持"联系人详情"
  • 初识 移动语义(Move Semantics)------ 高性能的秘密
    🕒 建议学习时间:3--4 小时|可分多次完成

💡 即使你从未手动 new/delete,也请认真学 ------ 这是现代 C++ 的基石!


📘 C++ 零基础入门教程(第 5 篇)

智能指针与 RAII ------ 让内存管理自动化


第一步:为什么需要智能指针?

❌ 传统 new/delete 的问题:

cpp 复制代码
void riskyFunction() {
    Student* s = new Student("Alice", 95); // 分配内存
    // ... 中间可能抛出异常或提前 return
    delete s; // 如果没执行到这行 → 内存泄漏!
}

🔥 内存泄漏:分配的内存未被释放,程序越跑越慢,最终崩溃。

✅ 解决方案:RAII(Resource Acquisition Is Initialization)

  • 资源(如内存、文件句柄)由对象的生命周期管理
  • 对象创建时获取资源,销毁时自动释放
  • 智能指针 = 自动 delete 的指针

第二步:std::unique_ptr ------ 独占所有权

unique_ptr 表示 唯一拥有 某块内存,不能复制,但可转移。

2.1 基本用法

cpp 复制代码
#include <iostream>
#include <memory>  // 必须包含
using namespace std;

class Widget {
public:
    Widget(string name) : name_(name) {
        cout << "Widget " << name_ << " 创建\n";
    }
    ~Widget() {
        cout << "Widget " << name_ << " 销毁\n";  // 自动调用!
    }
    void doWork() { cout << name_ << " working...\n"; }

private:
    string name_;
};

int main() {
    // 创建 unique_ptr(推荐用 make_unique)
    auto w = make_unique<Widget>("A");

    w->doWork();  // 用 -> 调用成员函数

    // 函数结束时,w 自动销毁,调用 ~Widget()

    return 0;
}

✅ 输出:

复制代码
Widget A 创建
A working...
Widget A 销毁

🔑 关键点:

  • make_unique<T>(args...) 是创建 unique_ptr 的安全方式
  • 无需 delete!离开作用域自动释放
  • 不能复制:auto w2 = w; ❌ 编译错误
  • 可转移:auto w2 = std::move(w);

2.2 何时使用 unique_ptr

  • 当你确定只有一个所有者时(最常见场景)
  • 替代原始指针 + new/delete
  • 用于类成员(确保对象销毁时资源释放)

第三步:std::shared_ptr ------ 共享所有权

shared_ptr 使用引用计数,多个指针共享同一对象,最后一个销毁时才释放内存。

示例:

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<Widget> p1 = make_shared<Widget>("B");
    {
        shared_ptr<Widget> p2 = p1;  // 引用计数 = 2
        cout << "p2 created, use_count=" << p1.use_count() << endl;
    } // p2 离开作用域,引用计数 = 1

    cout << "after p2 destroyed, use_count=" << p1.use_count() << endl;

    return 0; // p1 销毁,引用计数=0 → 自动 delete
}

✅ 输出:

复制代码
Widget B 创建
p2 created, use_count=2
after p2 destroyed, use_count=1
Widget B 销毁

⚠️ 注意:

  • make_shared<T>() 创建(比 make_unique 更高效)
  • 避免循环引用(否则内存永不释放 → 用 weak_ptr 解决,本篇不展开)

第四步:智能指针 vs 原始指针

场景 推荐
拥有资源(如 new 出的对象) unique_ptr / shared_ptr
仅观察/临时访问(不拥有) 原始指针引用
函数参数传递 ✅ 优先用 const 引用

🎯 黄金法则
永远不要写 newdelete

make_unique / make_shared 代替。


第五步:实战项目 ------ 智能指针版通讯录(支持联系人详情)

我们将升级第 4 篇的通讯录:

  • 每个联系人可附加"备注"(动态分配)
  • unique_ptr 管理备注,避免泄漏

✅ 改造 Contact 类:

cpp 复制代码
// contact_smart.cpp
#include <iostream>
#include <vector>
#include <string>
#include <memory>  // for unique_ptr
using namespace std;

class Contact {
public:
    string name;
    string phone;
    unique_ptr<string> note;  // 备注(可选)

    Contact(string n, string p) : name(n), phone(p) {}

    void setNote(const string& n) {
        note = make_unique<string>(n);  // 自动管理内存
    }

    void print() const {
        cout << name << "\t" << phone;
        if (note) {
            cout << "\t[备注: " << *note << "]";
        }
        cout << endl;
    }
};

class ContactBook {
private:
    vector<Contact> contacts;

public:
    void addContact(const string& name, const string& phone) {
        contacts.emplace_back(name, phone);  // 直接构造,避免拷贝
        cout << "已添加 " << name << endl;
    }

    void addNoteTo(const string& name, const string& noteText) {
        for (auto& c : contacts) {
            if (c.name == name) {
                c.setNote(noteText);
                cout << "已为 " << name << " 添加备注" << endl;
                return;
            }
        }
        cout << "未找到 " << name << endl;
    }

    void listAll() const {
        if (contacts.empty()) {
            cout << "通讯录为空" << endl;
            return;
        }
        cout << "\n--- 通讯录 ---\n";
        for (const auto& c : contacts) {
            c.print();
        }
    }
};

int main() {
    ContactBook book;
    book.addContact("Alice", "13800138000");
    book.addContact("Bob", "13900139000");

    book.addNoteTo("Alice", "同事,前端工程师");
    book.listAll();

    return 0;
}

✅ 输出:

复制代码
已添加 Alice
已添加 Bob
已为 Alice 添加备注

--- 通讯录 ---
Alice	13800138000	[备注: 同事,前端工程师]
Bob	13900139000

🎉 亮点:

  • noteunique_ptr<string>,即使不设备注也不会浪费内存
  • 对象销毁时,note 自动释放,零泄漏风险
  • 使用 emplace_back 直接构造 Contact,避免临时对象拷贝

第六步:初识移动语义(Move Semantics)

问题:vector.push_back(大对象) 会拷贝,很慢!

✅ 解决方案:移动(Move)而非拷贝

  • 把资源"转移"给新对象,原对象变空
  • 适用于 unique_ptrvectorstring

示例:

cpp 复制代码
vector<string> words;
string big = "非常长的字符串...";

words.push_back(big);        // 拷贝(慢)
words.push_back(move(big));  // 移动(快!big 变为空)

💡 在你的代码中:

  • emplace_back(args...)push_back(T(args)) 更高效
  • 返回局部对象时,编译器自动移动(无需 move

📌 本篇小结:你已掌握

概念 说明
RAII 资源由对象生命周期管理
unique_ptr 独占所有权,自动 delete,不可复制
shared_ptr 共享所有权,引用计数,最后销毁
make_unique / make_shared 安全创建智能指针
移动语义 避免拷贝,提升性能
现代 C++ 原则 不写 new/delete,用 STL + 智能指针

✅ 下一步建议

  1. 尝试 :将成绩系统中的 Student 改为用 unique_ptr 存储(虽然没必要,但可练习)
  2. 思考 :如果两个 Contact 互相引用对方(循环),shared_ptr 会泄漏吗?如何解决?
  3. 预习:什么是"模板"?如何写出通用算法?

相关推荐
陳10301 分钟前
C++:map和set的使用
开发语言·c++
苏宸啊3 分钟前
list底层实现
c++·list
近津薪荼3 分钟前
递归专题(4)——两两交换链表中的节点
数据结构·c++·学习·算法·链表
2501_940315264 分钟前
【无标题】2390:从字符串中移除*
java·开发语言·算法
乐观勇敢坚强的老彭6 分钟前
c++寒假营day01下午
c++·算法
lly20240610 分钟前
jEasyUI 树形菜单添加节点
开发语言
IT研究所12 分钟前
信创浪潮下 ITSM 的价值重构与实践赋能
大数据·运维·人工智能·安全·低代码·重构·自动化
AI职业加油站13 分钟前
Python技术应用工程师:互联网行业技能赋能者
大数据·开发语言·人工智能·python·数据分析
散峰而望16 分钟前
【算法竞赛】树
java·数据结构·c++·算法·leetcode·贪心算法·推荐算法
鱼很腾apoc19 分钟前
【实战篇】 第14期 算法竞赛_数据结构超详解(下)
c语言·开发语言·数据结构·学习·算法·青少年编程