三分钟掌握C++内存管理精髓 :这三个指针绝对让你相见恨晚!

开发
是不是经常被指针搞得焦头烂额?那些烦人的内存泄漏和悬空指针让你夜不能寐?别担心,今天我要介绍一个超级英雄 - unique_ptr!它会让你的指针烦恼一扫而空!

嘿,亲爱的开发者们!还在为那些烦人的内存泄漏而头疼吗? 😩 还在深夜被野指针搞得睡不着觉吗? 😫 是不是觉得指针管理就像是在玩俄罗斯轮盘赌? 🎲

别担心!今天我要介绍三位超级英雄,他们将彻底改变你写代码的方式! 🦸♂️

让我们认识一下这三位神奇的角色:

  • unique_ptr: 独来独往的孤胆英雄 🦹
  • shared_ptr: 团结友爱的好管家 👨👩👧👦
  • weak_ptr: 神出鬼没的观察者 👀

准备好了吗?让我们开始这段奇妙的智能指针之旅吧! ✨

提示:阅读本文后,你会发现原来指针管理也可以这么简单! 🎯

一、智能指针三剑客之 unique_ptr - 让资源管理不再头疼!

哎呀!你是不是经常被指针搞得焦头烂额?🤯 那些烦人的内存泄漏和悬空指针让你夜不能寐?别担心,今天我要介绍一个超级英雄 - unique_ptr!它会让你的指针烦恼一扫而空! 🦸♂️

1.为什么它这么厉害? 

想象一下,如果有一个既安全又高效的指针,用起来就像普通指针一样简单,是不是很棒?没错,unique_ptr 就是这样一个神奇的存在!它就像是给你的指针加上了一层防护罩,再也不用担心内存泄漏啦! 🛡️

2.传统写法 vs 现代写法 - 一场惊心动魄的对决! 

让我们先来看看传统写法是如何玩火的... 准备好了吗? 😱


Investment* makeInvestment() {
    Investment* p = new Stock("GOOGL", 50);  
    // 危险!这里要是抛个异常...
    // 这块可怜的内存就要变成孤魂野鬼啦! 👻
    return p;  // 默默祈祷调用者还记得delete... 🙏
}

void investMoney() {
    Investment* p = makeInvestment();
    // ... 中间代码 ...
    if(market_crash) {
        return; // 完蛋!忘记delete了! 💥
    }
    delete p;  // 手抖地敲下delete,生怕重复删除... 😰
}

但是等等!现代C++给我们带来了救星! ✨ 让我们看看超级英雄 unique_ptr 是如何化腐朽为神奇的:

// 现代写法 - 优雅得让人想哭! 😭
unique_ptr<Investment> makeInvestment() {
    return make_unique<Stock>("GOOGL", 50);  // 异常安全?早就搞定了! 💪
}

void investMoney() {
    auto investment = makeInvestment(); 
    // ... 中间代码 ...
    if(market_crash) {
        return; // 放心回家吧,unique_ptr会处理好一切! 👋
    }
} // 挥一挥衣袖,不带走一片内存~ 🌟

// 还想更酷一点?来看看这个! 🎩
void investMoneyWithCustomDeleter() {
    auto deleter = [](Investment* p) {
        cout << "优雅地清理投资..." << endl;
        delete p;
    };
    
    unique_ptr<Investment, decltype(deleter)> 
        investment(new Stock("TSLA", 200), deleter);
    // ... 
} // 就是这么专业! 🎯

看到差别了吗?这就是传说中的"代码自由"! 让我们永远告别手动内存管理的噩梦吧! 🎉

3.unique_ptr 解决了哪些问题? - 超级英雄的四大神技! 

(1) 内存泄漏? 哈!那是什么东西? 🤔

  • 析构函数自动出击,片甲不留! 💪
  • 就算异常来捣乱,也能全身而退! 🛡️

(2) 重复释放? 做梦去吧! 😴

  • 独占所有权,一山不容二虎! 👑
  • 转移时自动置空,永不走空! ✨

(3) 忘记释放? 这事儿交给我! 🤝

  • 作用域结束自动清理,就像家务小能手! 🧹
  • delete?那是什么古老的咒语? 📜

(4) 所有权不明确? 我的地盘我做主! 🏰

  • 移动语义明明白白,清清楚楚! 🚚
  • 编译器都帮你盯着,有问题立马报警! 🚨

二、智能指针三剑客之 shared_ptr - 让资源共享不再是噩梦!

还在为多个对象共享同一个资源而烦恼吗?😩 资源释放的时机让你头疼不已?😫 别担心,今天我要介绍的这位超级英雄 - shared_ptr 能帮你轻松解决这些问题! 🦸♂️

1.为什么你需要这位英雄?

想象这个场景:你有一张超高清壁纸,好几个窗口都想用它做背景。如果每个窗口都复制一份,那内存岂不是要爆炸?😱

但是如果用我们的英雄 shared_ptr,问题就迎刃而解了!它就像一个带计数器的管家,帮所有人管理这份共享的资源。等到最后一个使用者说"不用了",管家才会把资源收起来。优雅不优雅?😎

2.看看不用智能指针的恐怖故事

// 传统写法 - 这简直就是一个噩梦般的故事... 👻
class Widget {
    BigImage* image;  // 一个危险的野指针,像定时炸弹! 💣
public:
    Widget(BigImage* img) : image(img) {}
    ~Widget() { 
        // delete image; // 删还是不删?这是一个世纪难题! 🤔
        // 删了会不会导致程序爆炸?不删会不会变成幽灵在内存里游荡?
    }
};

void scaryExample() {
    // 故事开始于一个深夜... 🌙
    BigImage* img = new BigImage("huge.jpg");  // 召唤出一个神秘的指针

    Widget w1(img);  // 第一个对象说:"这是我的!"
    Widget w2(img);  // 第二个对象说:"不,这也是我的!"
    
    // 现在的情况变得很微妙... 😰
    // - 谁才是真正的主人?
    // - 谁该负责清理?
    // - 如果都删除会发生什么?
    // - 如果都不删除又会怎样?
    
    // 程序员开始失眠了... 😫
    // 这段代码就像一个定时炸弹,随时可能爆炸!
    // 让我们快点看看智能指针是如何拯救世界的! ➡️
}

想知道如何化解这个危机吗?且听下回分解,看看 shared_ptr 如何华丽登场! ✨

3.见证奇迹的时刻 - shared_ptr 闪亮登场! 

class Widget {
    shared_ptr<BigImage> image;  // 请看!超级管家驾到! 🎩
public:
    Widget(shared_ptr<BigImage> img) : image(img) {}
    // 析构函数?哈!让管家来操心这些琐事吧! 🧐
};

void amazingExample() {
    // 🎭 第一幕:创建共享资源
    auto img = make_shared<BigImage>("huge.jpg");  // 管家:新资源已就位!
    
    // 🎭 第二幕:资源共享的魔法时刻
    Widget w1(img);  // 管家掏出小本本:✍️ "好的,第一位使用者登记完毕!"
    Widget w2(img);  // 管家继续记录:✍️ "第二位来了,已经有两位了呢~"
    
    // 🎭 第三幕:完美谢幕
    // 不用操心善后工作
    // 当最后一位演员退场时
    // 管家会优雅地清理一切
    // 就像变魔术一样! 🎩✨
} // 管家微笑着:一切尽在掌控之中! 😌

想知道这位神通广大的管家还有什么惊人绝技吗?且听下回分解! 🎬

4. unique_ptr vs shared_ptr - 谁才是你的真命天子? 

让我们来看看这两位C++世界的顶级高手之间的终极对决! 🥊

虽然 unique_ptr 是个独来独往的侠客,但有时候我们需要一个更会"社交"的伙伴。这时候,就轮到我们的 shared_ptr 大显身手啦! ✨

(1) 资源共享场景 - 独行侠遇到的困境 🌋

// unique_ptr: "对不起,我不会分身术..." 😅
unique_ptr<Config> config = loadConfig();
// worker1: "我要配置!"
// worker2: "我也要!"
// unique_ptr: "但我只能跟一个人走..." 
// 场面一度很尴尬... 😱

// shared_ptr: "让我来解决这个问题!" 🦸♂️
shared_ptr<Config> config = make_shared<Config>();
worker1->setConfig(config);  // "给你一份!"
worker2->setConfig(config);  // "你也有!"
// 所有人开开心心地共享资源,皆大欢喜! 🎉

(2) 生命周期管理 - 是时候展现真正的技术了! 🎭

  • unique_ptr: "我要准确知道什么时候说再见" 📅
  • shared_ptr: "放心交给我,我会照顾好一切" 🤗
  • 异步任务和回调函数: "终于等到你!" 💝

(3) 缓存系统 - 共享才是王道 🏰

class Cache {
    // shared_ptr: "让我来当这个资源管家!" 
    unordered_map<string, shared_ptr<Resource>> resources;
public:
    shared_ptr<Resource> get(const string& key) {
    // "不管多少人来要资源,我都能应付自如~" 😎
    // "用完自动收拾,完全不用操心!" ✨
    return resources[key];
    }
};

记住: unique_ptr 是独行侠, shared_ptr 是社交达人,要根据场景选择合适的英雄! 🎯

提示: 虽然 shared_ptr 很强大,但也别忘了它的社交能力是要付出代价的(性能开销)哦! 🤓

5. shared_ptr 的超能力

(1) 自动计数功能

  • 新人用资源时 +1
  • 不用了就 -1
  • 没人用了就自动清理
  • 就像一个尽职尽责的管家! 👔

(2) 线程安全防护

  • 计数器的加减都是原子操作
  • 多线程环境也完全不怕
  • 简直就是多线程克星! 🛡️

(3) 还能自定义清理方式

shared_ptr<File> fp(fopen("test.txt", "r"), 
    [](FILE* f){ fclose(f); });  // 优雅~

记住:共享不是免费的,shared_ptr 比 unique_ptr 有更多开销。所以要根据实际需求选择合适的智能指针! 🎯

三、智能指针三剑客之 weak_ptr - 打破循环引用的救星!

还在为 shared_ptr 循环引用导致的内存泄漏而烦恼吗? 😫 weak_ptr 来救场啦! 它就像是 shared_ptr 的好朋友,可以观察但不会干扰计数,完美解决循环引用问题! 🦸♂️

1.为什么需要它?

想象这个场景:你有两个类互相引用对方。如果都用 shared_ptr,那引用计数永远不会变成0,资源永远不会释放! 这就是著名的"循环引用"问题。😱

但是用了 weak_ptr,它就像一个"旁观者",可以看到对象是否还活着,但不会影响它的生命周期。完美! 👀

2.看看不用它有多可怕

想象一下,这段代码就像是在讲述两个好朋友 Lucy 和 Lily 的故事:

// 糟糕的设计 - 内存永远不会释放! 
class Person {
    string name;
    shared_ptr<Person> best_friend;  // 相互引用
public:
    Person(const string& n) : name(n) {}
    void makeFriend(shared_ptr<Person> friend_) {
        best_friend = friend_;
    }
};

void createFriends() {
    auto lucy = make_shared<Person>("Lucy");
    auto lily = make_shared<Person>("Lily");
    
    lucy->makeFriend(lily);  // Lucy的引用计数变成2
    lily->makeFriend(lucy);  // Lily的引用计数变成2
    
    // 函数结束时,两个对象都还有一个引用
    // 所以永远不会被删除!
} // 内存泄漏! 😱

为什么这是个问题?

(1) 死循环的友谊

  • Lucy说:"我要永远抓住Lily!" (引用计数+1)
  • Lily说:"我也要永远抓住Lucy!" (引用计数+1)
  • 结果: 两个人都放不开对方,永远被困在内存里! 🔄

(2) 内存泄漏的后果

  • 系统: "该清理了!"
  • Lucy: "不行!我还抓着Lily呢!"
  • Lily: "我也抓着Lucy呢!"
  • 系统: "好吧..." (无奈脸) 😮💨

这就像两个人互相拉着对方的手,都不愿意先放开。结果就是两个人都走不了,永远站在那里! 🧍♀️🧍♀️

这就是为什么我们需要 weak_ptr - 它就像是一个"松散的握手",可以随时放开,不会造成这种尴尬的永恒循环!

3. weak_ptr 英雄登场!

让我们看看如何用 weak_ptr 来优雅地解决循环引用问题。在这个例子中,我们将创建两个可以互相成为好朋友的 Person 对象,但这次我们使用 weak_ptr 来存储朋友关系,这样就不会造成循环引用了! 🎯

class Person {
    string name;
    weak_ptr<Person> best_friend;  // 改用weak_ptr
public:
    Person(const string& n) : name(n) {}
    void makeFriend(shared_ptr<Person> friend_) {
        best_friend = friend_;  // 不会增加引用计数
    }
    
    void meetFriend() {
        // 需要时尝试提升为shared_ptr
        if (auto friend_ptr = best_friend.lock()) {
            cout << "见到好朋友: " << friend_ptr->name << endl;
        } else {
            cout << "朋友已不在..." << endl;
        }
    }
};

void createFriends() {
    auto lucy = make_shared<Person>("Lucy");
    auto lily = make_shared<Person>("Lily");
    
    lucy->makeFriend(lily);  // 不会增加引用计数
    lily->makeFriend(lucy);  // 不会增加引用计数
    
} // 完美释放! ✨

看到了吗?通过使用 weak_ptr,我们不仅解决了循环引用的问题,还增加了一个 meetFriend() 方法来安全地检查朋友是否还存在。当需要访问好朋友时,我们使用 lock() 方法来获取一个临时的 shared_ptr,这样就能安全地访问对象了。如果对象已经被释放,lock() 会返回一个空指针,让我们能够优雅地处理这种情况。这就是 weak_ptr 的魔力! ✨

4. weak_ptr 的超能力

(1) 观察但不占有

  • 不会增加引用计数
  • 可以安全地观察对象是否存在
  • 完美解决循环引用问题

(2) 安全检查机制

  • 使用前需要先检查对象是否还活着
  • 通过 lock() 获取 shared_ptr
  • 避免访问已释放的对象

(3) 常见使用场景

class EventManager {
    weak_ptr<Widget> widget;  // 不影响Widget的生命周期
public:
    void setWidget(shared_ptr<Widget> w) {
        widget = w;
    }
       
    void notify() {
        if (auto w = widget.lock()) {
            w->onEvent();  // 安全调用
        }
    }
};

(4) 使用建议

  • 用于打破循环引用
  • 观察者模式中使用
  • 缓存系统中使用
  • 需要对象存在性检查的场景

记住:weak_ptr 是观察者而非所有者,它让你的代码更加安全可靠! 

四、智能指针三剑客总结

(1) unique_ptr - 独行侠 🦹

  1. 性格: "我是独行侠,不跟任何人共享资源!"
  2. 特长: 自动清理、零开销、移动转移
  3. 口头禅: "这是我的地盘,我说了算!" 💪

(2) shared_ptr - 社交达人 👨👩👧👦

  • 性格: "来来来,大家一起用,有我在不用担心!"
  • 特长: 引用计数、自动清理、多人共享
  • 口头禅: "我的资源就是你的资源~" 🤝

(3) weak_ptr - 神秘观察者 👀

  • 性格: "我就看看,不参与,不计数~"
  • 特长: 打破循环引用、安全观察、不影响生命周期
  • 口头禅: "我只是个观察者,随时可以放手" 🎭

他们的口号是:

"再见了,内存泄漏!永别了,野指针!C++的世界,有我们守护!" ✨

使用建议:

  • 默认选择: unique_ptr (除非你真的需要共享)
  • 需要共享: shared_ptr (记住要付出性能代价哦)
  • 循环引用: weak_ptr 来救场!

记住:选择合适的智能指针,就像选择超级英雄一样重要!让我们一起创造更安全、更优雅的代码世界吧!🌈

责任编辑:赵宁宁 来源: everystep
相关推荐

2021-12-17 07:47:37

IT风险框架

2022-03-26 09:06:40

ActorCSP模型

2020-05-06 09:18:56

Pandas函数大数据技术

2025-02-13 08:04:49

spliceCPU数据

2024-05-16 11:13:16

Helm工具release

2024-12-11 12:00:00

C++拷贝

2009-11-09 12:55:43

WCF事务

2024-12-18 10:24:59

代理技术JDK动态代理

2018-02-07 14:54:17

Riverbed应用性能管理数字体验

2021-04-14 09:22:18

Python技巧交换变量值

2023-12-27 08:15:47

Java虚拟线程

2024-08-30 08:50:00

2022-02-17 09:24:11

TypeScript编程语言javaScrip

2024-01-16 07:46:14

FutureTask接口用法

2021-04-20 13:59:37

云计算

2020-06-30 10:45:28

Web开发工具

2013-06-28 14:30:26

棱镜计划棱镜棱镜监控项目

2025-02-24 10:40:55

2024-01-29 00:51:39

前端开发利器

2015-10-27 10:12:21

r语言命令相见恨晚
点赞
收藏

51CTO技术栈公众号