《 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. 预习:什么是"模板"?如何写出通用算法?

相关推荐
宇钶宇夕2 小时前
CoDeSys入门实战一起学习(五):CoDeSys V3 车库门控制编程全解析系列(手册基础第五篇)
运维·自动化
零度@2 小时前
Java 消息中间件 - 云原生多租户:Pulsar 保姆级全解2026
java·开发语言·云原生
jghhh012 小时前
基于MATLAB的分块压缩感知程序实现与解析
开发语言·算法·matlab
%xiao Q2 小时前
信息学奥赛一本通(部分题解)
c语言·c++·算法
w-w0w-w2 小时前
C++ list简单模拟实现
数据结构·c++
旦莫2 小时前
使用OCR加持的APP自动化测试
python·测试开发·自动化·ocr·pytest·ai测试
枫叶丹42 小时前
【Qt开发】Qt系统(六)-> Qt 线程安全
c语言·开发语言·数据库·c++·qt·安全
你怎么知道我是队长2 小时前
C语言---错误处理
c语言·开发语言
qyresearch_2 小时前
直线导轨:精密制造的“隐形冠军”,驱动工业自动化升级的核心力量
人工智能·自动化·制造