# C++ 智能指针解析

面试高频指数:★★★★☆

# 为什么需要智能指针

众所周知,Java 和 C/C++ 中间隔着一堵由内存动态分配和垃圾回收机制所围成的墙。

Java 大佬们经常吐槽 C++ 没有垃圾回收(Gabage Collector)机制,而 C++ 爱好者也经常攻击 Java 限制太死,不够灵活。

其实 Java 并不是最早实践内存动态分配和垃圾自动回收机制的语言,这个构想在 1960 年就已经在MIT 的教学语言 Lisp 中提出。

在 C/C++ 中最为灵活的工具就是指针了,但指针也是很多噩梦的源头,内存泄露(memory leak)和内存非法访问应该算是 C++ 程序员的家常便饭了。

但是又不能抛弃指针带来的灵活性,不过幸好 C++ 里有了智能指针,虽然在使用上有局限性,但是能够最大程度减少程序员手动管理指针生命周期的负担。

指针的强大很大程度上源于它们能追踪动态分配的内存。通过指针来 管理这部分内存是很多操作的基础,包括一些用来处理复杂数据结构 的操作。要完全利用这些能力,需要理解C的动态内存管理是怎么回 事。

C++是面向内存编程,Java是面向数据结构编程。

C++里,内存是裸露的,可以拿到地址,随意徜徉,增了删了,没人拦你,等到跑的时候再崩给你看。

Java里,能操作的都是设计好的数据结构,array有长度,String不可变,每一个都是安全的,在内存和程序员之间,隔着JVM,像是包住了边边角角的房间,随便小孩折腾,不会受伤。

Java程序员是孩子,嚷嚷要这个那个,玩完了就丢,JVM是家长,买买买,还要负责收拾。有的孩子熊点,屋子很乱,收拾起来费劲,但房子还在。

C++程序员是神,操纵着江河湖海,日月星辰,但能力越大,责任越大,万一新来的神比较愣,手一滑,宇宙就退出了。

新手写C++,像是抱着一捆指针,在浩瀚的内存中裸奔。跑着跑着,有的针掉了,不知踪影,内存就泄露了;跑着跑着,突然被人逮住,按在地上打的error纷飞,内存就越界了;终于到了,舒了口气,把针插在脚下,念出咒语,

“delete”

系统就崩溃了。

来源:围在Java和C++中的那堵墙 ! (opens new window)

原文:https://www.zhihu.com/question/51284083/answer/125338745 (opens new window)

# C/C++ 常见的内存错误

在实际的 C/C++ 开发中,我们经常会遇到诸如 coredump、segmentfault 之类的内存问题,使用指针也会出现各种问题,比如:

  • 野指针:未初始化或已经被释放的指针被称为野指针
  • 空指针:指向空地址的指针被称为空指针
  • 内存泄漏:如果在使用完动态分配的内存后忘记释放,就会造成内存泄漏,长时间运行的程序可能会消耗大量内存。
  • 悬空指针:指向已经释放的内存的指针被称为悬空指针
  • 内存泄漏和悬空指针的混合:在一些情况下,由于内存泄漏和悬空指针共同存在,程序可能会出现异常行为。
  • ....

# 智能指针

而智能指针是一种可以自动管理内存的指针,它可以在不需要手动释放内存的情况下,确保对象被正确地销毁。

这种指针可以显著降低程序中的内存泄漏和悬空指针的风险。

智能指针的核心思想就是 RAII,关于 RAII 请看这篇文章: 如何理解RAII (opens new window)

在C++中,智能指针常用的主要是两个类实现:

  • std::unique_ptr
  • std::shared_ptr

# std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它保证指向的内存只能由一个unique_ptr拥有,不能共享所有权。

当unique_ptr超出作用域时,它所指向的内存会自动释放。

下面是个例子:

#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> ptr(new int(10));
    std::cout << *ptr << std::endl; // 输出10
    // unique_ptr在超出作用域时自动释放所拥有的内存
    return 0;
}

# std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,它允许多个shared_ptr指向同一个对象,当最后一个shared_ptr超出作用域时,所指向的内存才会被自动释放。

举个栗子:

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> ptr1(new int(10));
    std::shared_ptr<int> ptr2 = ptr1; // 通过拷贝构造函数创建一个新的shared_ptr,此时引用计数为2
    std::cout << *ptr1 << " " << *ptr2 << std::endl; // 输出10 10
    // ptr2超出作用域时,所指向的内存不会被释放,因为此时ptr1仍然持有对该内存的引用
    return 0;
}

总的来说,智能指针可以提高程序的安全性和可靠性,避免内存泄漏和悬空指针等问题。

但需要注意的是,智能指针不是万能的,也并不是一定要使用的,有些场景下手动管理内存可能更为合适。

# 总结

这篇文章只是大概解释了一下智能指针是什么,以及有什么好处,我们接下来还会以 shared_ptr 的实现为例,带大家手写一下智能指针,而这也是面试中常考的知识:

深入理解shared_ptr之手写 (opens new window)

最新原创的文章都先发布在公众号,欢迎关注哦~,
扫描下方二维码回复「CS」可以获得我汇总整理的计算机学习资料~

编程指北图片
@2021-2024 编程指北 版权所有 粤ICP备2021169086号-2