一辆车在高速上以 80 公里时速跑着,前向摄像头每 30 毫秒回传一帧图像,毫米波雷达每 20 毫秒刷新一次目标列表,激光雷达单帧点云就要 10 多万个点,所有这些数据要在几十毫秒内完成处理、融合、预测、规划,最终变成方向盘、油门、刹车上的具体指令。这段链条里跑在最底层、压住整个性能基线的,就是 C++。打从这个行业开始,C++ 和自动驾驶系统这对组合就没分开过。
这篇东西想聊的,不是“C++ 有多牛”,而是从从业者的角度把这件事讲透:为什么自动驾驶系统绕不开 C++,C++ 在车里到底干了些什么活,真正上车的 C++ 工程又该怎么写、怎么调、怎么避坑。适合正准备入行自动驾驶、想搞明白底层技术选型,或者已经在写 C++ 但想往车端方向转的朋友。内容会尽量贴近实际项目,不玩虚的。
1. 为什么自动驾驶系统绕不开 C++
1.1 性能不是玄学,是物理规律
先算一笔粗糙的账。一辆带基础辅助驾驶功能的车,感知部分至少要同时处理 6 路摄像头、1 到 2 颗雷达、1 颗激光雷达。即便是做了降采样,每秒钟需要处理的原始数据量也轻松超过 200 MB,高峰期可能到 500 MB 以上。
处理这些数据不是“算完就行”,还有硬实时要求。AEB 自动紧急制动这类功能,从感知到决策再到执行,业内普遍要求端到端时延在 300 到 500 毫秒以内,关键感知链路的处理时延往往被压到 50 毫秒以内。如果你用 Python 写核心感知管线,光是一帧 1080P 图像做完预处理、缩放、颜色空间转换、归一化,再送到模型推理,再后处理解析,这一串动作在解释型语言里就很吃力了,更别提整条链路上还有多个节点的序列化、反序列化和拷贝。
C++ 的优势在于它能让你把内存布局、拷贝次数、缓存命中率都控制到自己手里。比如一张图像从摄像头驱动到推理引擎,中间能减少一次拷贝就减少一次,能用零拷贝的共享内存就用共享内存,这些优化放在 C++ 里是天经地义的做法,换到 Python 里则很难绕过解释器和运行时开销。性能在这个行业不是“跑得快一点”的加分项,而是决定系统能不能在物理时间内完成任务的硬指标。
1.2 确定性:自动驾驶更怕“卡一下”
很多人说自动驾驶选 C++ 是为了性能,其实还有比性能更要命的一点,叫作确定性。
车上的软件不是在服务器上跑,而是在一块算力有限的 embedded 平台上跑,周围环境随时在变,系统状态也可能混乱。以 Java 为例,JVM 的逃逸分析、即时编译、垃圾回收优化,在绝大多数后台场景里很优秀,但 GC 的 STW(Stop The World)会把最大暂停时间拉长到几十甚至几百毫秒。这在网页后端无感,在自动驾驶系统就是大事故:一次几百毫秒的停顿足够让车多跑出去十几米。
C++ 的哲学是把资源的分配、释放,以及最终的运行节奏都交到程序员手里。你可以在启动阶段把该分配的 Buffer 全部预分配好,运行期不再有隐式内存分配;你可以用数据池、对象池、无锁队列让关键路径上的每一微秒都变得可预测。这种“可控性”在功能安全标准 ISO 26262 的语境下,几乎就是刚需。功能安全评审的时候,审计员会反复追问:“这行代码最坏情况下要执行多久?”如果你用的是 C++,至少还能给出一个基于代码路径和实测的答案。
1.3 生态与工程惯例:ROS、Apollo、AUTOSAR
再说一个更现实的原因:自动驾驶软件生态本身就用 C++ 搭的。
ROS 的 C++ 客户端 roscpp 是行业里主要的使用路径,Robot Operating System 2(ROS 2)的底层实现也以 C++ 为核心,很多感知、规划模块都是直接基于 ROS 2 的节点模型用 C++ 写的。百度 Apollo 的 Cyber RT 通信框架、基础库和大部分算法模块是 C++。底层的 AUTOSAR Adaptive Platform 规范里,C++ 是官方指定的主要实现语言之一。很多芯片厂商推出的感知 SDK、ISP 库、算子库,对外开放的头文件也基本是 C/C++ 接口。
这意味着你进入自动驾驶行业后,读到的代码、接触到的中间件接口、能复用的算法库,绝大多数都是用 C++ 写的。与其说“选 C++”,不如说行业事实标准就是 C++。
1.4 对比其它语言:Python 能上车吗、Java 为什么没人用
Python 在自动驾驶里没有缺席,但它出现在模型训练、离线数据集处理、仿真脚本、算法原型验证这些环节。训练一个深度学习模型,PyTorch 的 Python API 很好用,但产出的是权重文件,真正跑推理用的 TensorRT、ONNX Runtime 的底层还是 C/C++。车端的感知模型部署、前处理、后处理基本是 C++ 的地盘,Python 版本的推理封装更多是用来做模型验证。
Java、C# 这些语言也不是“不能写算法”,而是在嵌入式实时系统这个场景里,运行时依赖、内存模型、中断响应能力都成了减分项。车载控制器追求的是可控、可预期、依赖少,C++ 在这几点上几乎无可替代。实际项目里偶尔会有 QNX 和 Linux 之间的移植问题,但语言本身的选择反而不是争议点:C++。
2. 一辆智能车里的 C++:能摸到的核心模块
2.1 感知管线:从图像到障碍物的 C++ 之旅
感知模块是自动驾驶中最吃算力、也最体现 C++ 工程能力的地方。
一台车上的感知系统大致是这么一条链路:首先是传感器驱动,通常是 camera 驱动、lidar 驱动、radar 驱动,用 C++ 写驱动层最大的特点是高频中断和 DMA Buffer 管理;然后是数据预处理,图像要去畸变、裁剪、缩放、色彩空间转换,点云要做直通滤波、体素降采样、地面分割;接下来送入模型推理,TensorRT 或者 TNN 这类推理引擎的宿主代码一定包含大量的 C++ 层,你需要维护 GPU 显存与 CPU 内存之间的同步;最后是后处理,目标框的 NMS 非极大值抑制、跟踪匹配、类别判定,这些部分几乎全是 C++ 写的。
我经常看到简历里写“熟悉深度学习模型部署”,但到了现场一问 NMS 是怎么实现的,很多人就卡住了。实际上在模型推理之前之后的这块代码,比模型本身更考验 C++ 功底。体素降采样要处理几万个点云,一个不合适的排序算法就能带来肉眼可见的时延抖动。后处理里的匈牙利匹配、卡尔曼滤波更新、IOU 计算,全是基础数据结构和算法问题。热词里出现“冒泡排序算法 c++”“快速幂算法 c++”“插入排序 c++”这些东西,表面上像是在刷题,但如果你是做感知模块的,底层这些复杂度分析、排序、查找、内存拷贝的功夫,会直接决定输出频率。
2.2 融合、预测与规划:算法落地的另一道坎
传感器融合很多人以为是最难的部分,其实真正难的是把不同传感器的时间戳对齐之后,放在同一个坐标系里做目标级融合。C++ 在这里的角色是提供一个稳定、高效的数据结构层。
举个例子,Radar 和 Camera 的融合,你要处理两类目标列表,各自有各自的坐标系、时间基准、置信度。域控平台没有 GPU 那么强的并行计算资源,每帧留给融合算法的时间往往只有 10 到 20 毫秒。这时候你用 C++ 怎么设计内存布局、怎么避免频繁的 vector 扩容、怎么用 SIMD 做点集变换,直接决定整个模块能不能塞进时间片里。
预测模块通常会跑多假设轨迹预测,对每个目标生成若干条轨迹并计算概率。规划模块则要在几十到几百毫秒内搜索出可行轨迹。这些模块里 C++ 的强项在于能精细管理轨迹池、代价地图、SP 样条曲线这些结构。很多人写算法只关心“逻辑对不对”,但上车以后逻辑对不是终点,“跑得足够快、占用足够小、最坏情况能兜底”才是。
2.3 控制与底层:最后 100ms 的 C++
规划模块输出一条轨迹以后,控制模块要把它换算成方向盘转角、油门开度和刹车压力,频率通常在 50 到 100 Hz。控制算法本身未必复杂,比如 PID、LQR、MPC,但控制器的稳定性很大程度上取决于“能不能每次都在固定时间周期内完成计算”。
干过嵌入式控制的人都有这种体验:一个控制环明明算法写好了,可一放到实车上就开始抖动,原因常常出在内存分配、锁竞争、系统调用上。C++ 工程化的控制模块,通常会把所有临时变量预分配好,控制周期内的计算路径不调用任何可能阻塞的系统接口,严格避免动态内存分配和文件 I/O。这种写法看起来有点“洁癖”,但在真实的车控场景里,每一处隐患都可能引发抖动,而控制环一抖,乘客的感受就是闯动。
2.4 通信与中间件:链接所有节点的那根“总线”
感知、融合、规划、控制这些模块需要通信。车内部署的方案基本是 DDS 或者共享内存,再上层包一层类似 Cyber RT 的框架。C++ 在这里承担的是消息的序列化、反序列化、发布订阅、共享内存读写等底层逻辑。
说一个实际开发里很常见的坑:DDS 消息里如果塞了一个大的 vector,每次发布都要做一次深拷贝,频率一高 CPU 占用率就爆表。C++ 工程里通常会用 move 语义把数据的所有权转出去,或者直接共享内存 + 原子标志位来避免拷贝。热词里提到“C++ 回调函数例子”,在中间件的事件回调上非常典型。发布订阅模型本质就是事件回调,但回调里不能做耗时操作,否则会阻塞 IO 线程,这条原则我几乎在每个项目里都要重申。
3. 从“能编译”到“能上车”:C++ 工程化实战
3.1 内存管理:RAII 和智能指针的正确打开方式
很多从 Linux C 转过来的同学总习惯malloc/free,上了几年 C++ 车端项目以后,我强烈的建议是:只要不是硬实时控制路径,优先用 RAII 和智能指针管理资源,用std::unique_ptr表达独占所有权,用std::shared_ptr表达共享所有权。裸指针只保留在确有必要的地方,比如传给第三方 SDK 的 C 接口。
为什么?因为车端代码改动频繁、多人协作,裸指针最容易在异常分支或提前返回处泄漏。RAII 的做法跟你用std::ofstream一样,作用域结束自动析构,资源自动释放。我在代码审查里经常看到有人在构造函数里new一块内存,然后在析构函数里delete,其实直接用一个std::unique_ptr成员就够了,还能自动处理异常安全。这个习惯不是“炫技”,是生存之道。
也要提醒一点:智能指针不是万能的,shared_ptr的引用计数本身有原子操作开销,高频触发的回调里如果频繁拷贝shared_ptr,可能比裸指针多出几十纳秒。在控制环路这种极致路径上,还是要结合具体硬件 profile 之后再做权衡。
3.2 线程模型与锁:实时系统如何避免“互相等待”
自动驾驶系统天生多线程:感知线程、融合线程、规划线程、控制线程、日志线程、远端通信线程。线程一多,锁就来了,可锁也是实时系统最大的敌人。我见过最典型的故障:模块 A 持有锁后做了一次较重的计算,模块 B 在另一个线程等锁,导致整条规划链路产生 80 毫秒的时延抖动,正好触发安全策略,车从自动驾驶降级成人工接管的警告。
上车级的工程实践,要尽量做到两点。第一,细粒度锁,只保护真正需要保护的那几行数据,不让锁覆盖大段计算逻辑;第二,核心路径上用无锁数据结构。C++11 以后的std::atomic配合无锁队列,在很多自动驾驶中间件里已经是标配。但无锁不是银弹,ABA 问题就是无锁结构里的经典陷阱,std::atomic的 CAS 循环如果不做 ABA 处理,可能拿到一个中间状态。热词里恰好有“aba 问题c++”,说明很多同学已经注意到这块了。
我的建议是,能用消息传递就别共享数据,能用std::atomic就别用互斥锁,实在逃不开锁的时候就测最坏时延,而不是平均时延。实时系统压测看的是尾巴,不是平均值。
3.3 构建、部署与依赖:CMake、交叉编译和运行库
写了 C++ 不上车不算完,上了车还要解决环境问题。车端芯片通常是 ARM Cortex-A 系列,或者英伟达 Orin 这类异构平台,编译一般分 x86 主机交叉编译和板端本地编译两条路。交叉编译意味着你要维护一套目标平台的 sysroot 和交叉编译工具链。CMake 几乎是这个环节唯一现实的选择,工具链文件(toolchain file)里要把编译器、链接器、目标系统库路径都指对,稍有偏差就会出现“编译过了,一跑就段错误”的玄学问题。
这里踩过的坑实在太多了。最常见的是本机编译依赖了宿主机的 glibc 版本,板子上的固件版本太老,跑起来报 GLIBC_2.29 not found。还有 OpenCV、Eigen、Boost 这些依赖库必须选择与目标板匹配的版本和交叉编译产物,不能图省事直接把 x86 的 so 库丢上去。有一个细节:在 Windows 或 Linux 上开发,如果某个库是通过 Visual Studio 的运行时组件安装的,到了部署环境就得确认对应的 VC++ Redistributable 或系统运行库是否齐全,否则程序会在启动阶段报缺失,这类问题排查起来最浪费时间,建议第一轮就把部署环境清单理清楚。
我个人的习惯是:项目一开始就把交叉编译的 Docker 镜像固定下来,依赖库的版本全部锁定,用 CMake 的find_package或 vcpkg 做版本管理。热词里出现“microsoft visual c++ 2015-2022 redistributable (x64) 下载”,很多初学者可能以为这是游戏安装依赖,其实 Windows 桌面端的 C++ 工程部署也离不开这一套运行时,上位机工具、数据回放工具、仿真器往往就靠它活着。
3.4 日志与监控:spdlog 上车前的三个配置细节
C++ 工程里日志库用得最多的是 spdlog,它性能好、接口简单、支持异步落盘。我用了几年后总结出三个上车前必须注意的细节。
第一,异步模式下要设置合理的队列大小。默认队列可能只有几 MB,一旦日志量突然增大(比如一场测试里点云日志全量打印),队列满了就会丢日志。更危险的是某些配置下队满会阻塞业务线程,直接影响控制链路。
第二,日志格式里一定要带毫秒时间戳和线程号。自动驾驶排问题时,没有时间戳的日志基本没法用,多线程日志没有线程号等于灾难。我自己习惯把日志格式统一成[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v,每一行日志都能精确到毫秒,然后和 rosbag、数据包里的时间线对齐。
第三,不要把业务代码和日志代码混在一起。有人图省事在关键路径里直接spdlog::info,结果上线后发现延迟飙高。正确的做法是先用一个轻量的环形缓冲区记录数据,再由专门的日志线程批量落盘,核心路径上不做 IO。
热词里还有“c++ spdlog”“tdengine, c++绑定写入数据库”,我也多说一句:传感器数据、日志这类带时间线的数据,在离线分析时很适合用 TDengine 这类时序数据库存储。用它的 C++ 绑定(比如taos_stmt_prepare这类预编译接口)批量写入,比一条条 insert 快一到两个数量级,而且能保留完整的时间戳,方便把日志和传感器数据对齐。数据回放工具、道路测试分析平台基本都离不开这个思路。
3.5 代码质量门禁:AUTOSAR C++ 和静态检查
自动驾驶系统不是写完了能跑就行,它有功能安全要求。ISO 26262 对软件开发的指南对应到代码规范上,通常就是 AUTOSAR C++14、MISRA C++ 或者 HIC++。很多没接触过车规代码的人觉得这些规范“太严格,管太宽”,比如“不许用new”“循环变量不许用整型”“函数出口只能有一个”等等。
可这些约束不是拍脑袋定的。以“函数出口只能有一个”为例,它的初衷是避免资源在提前 return 时没有释放;而“不许用new”是为了避免运行时内存分配不确定性,替代方案是在启动阶段用内存池预分配。AUTOSAR C++ 本质上不是教你“写成什么样的代码”,而是教你在做一个高风险系统时,怎么把不可控因素降到最低。
我在项目里是会把这些规则落到自动化门禁里的,比如 clang-tidy 配合.clang-tidy配置文件,CI 里跑一轮静态检查,违规直接拦截。第一次把这类检查引入团队时大家确实抱怨了一段时间,但半年以后,线上疑难故障率明显下降了。写代码的时候肩膀上压一个“规则提醒器”,比事后排查崩溃要舒服得多。
4. 上车调试实录:我踩过的 7 个 C++ 深坑
4.1 缓存一致性:为什么数据“明明写入却没生效”
在 ARM 多核平台上最容易踩的坑就是缓存一致性。某一次测试里,线程 A 往共享内存里写入了一帧感知结果,线程 B 却读到旧数据,排查来排查去,代码逻辑看起来完全没问题,性能也正常。最后才发现问题出在没有正确的内存屏障或者缓存同步操作。
现代 CPU 为了性能会乱序执行指令,不同核之间的缓存也不是实时同步的。当你用共享内存做线程间通信时,std::atomic默认带顺序一致性,问题不大;但如果用裸指针加普通变量,编译器和 CPU 都可能重排指令。解决方式是在关键写点加std::atomic_thread_fence或直接使用带 proper memory order 的原子变量。很多车端中间件之所以用共享内存加无锁队列,并不是因为它比锁更“快”,而是因为它可预测,但前提是你真的懂 barrier 和 atomic。
4.2 浮点误差:同一份地图,为什么两辆车结果不同
还有一次印象很深的调试经历,两辆同型号测试车加载同一份高精地图数据,在同一路段跑,规划结果却不一样。排查到最后是浮点精度问题:一辆车的定位模块输出的是 double,另一辆因为历史原因被降成了 float,导致后续轨迹采样和代价计算产生毫厘级的偏差,最终被累积放大。
在 C++ 工程里,浮点类型不一致是潜在的隐形炸弹。同一份代码,在 x86 上某段计算用到了 80 位扩展精度,交叉编译到 ARM 后变成 64 位,结果就可能不同。解决思路很简单但很关键:在模块边界做类型统一,能全部 double 就全 double,关键量不要混用 float 和 double;比较浮点数时不要用==,而是设置 epsilon,或者直接用绝对误差加相对误差的混合判据。热词里“判断质数c++优化”这类算法题大家都刷得飞起,但真实生产环境里一个浮点问题比十个质数算法都难查。
4.3 定时器漂移与时间戳:一个并发 BUG 的典型现场
曾经排查过一个诡异的问题:融合模块偶尔比预期慢 30 毫秒,但没有规律,持续很久也复现不了。后来加了日志发现,模块订阅的某个 topic 时间戳偶尔会出现比上一帧还早的情况,导致缓存队列里一直有“无法处理”的旧帧,处理线程陷入等待。
根子在于传感器驱动发出数据的时钟源不统一,一个用的是系统时钟,一个用的是硬件定时器,两者之间有一点漂移。在车上,时间戳的精度和一致性比代码更关键。我的做法是:所有模块尽量使用统一的时钟源(如 gptp 或 PTP 同步的硬件时钟),消息接口里把 "时间戳的时钟域" 也带上,不做想当然的“同一个系统就是同一个时间”。上车之前,建议先集中做一次时间戳一致性测试,否则后面排查会非常痛苦。
4.4 数据竞争与 ABA:无锁队列没那么简单
无锁队列听起来高级,实践起来到处是雷。之前为一个高性能传感器数据通路引入过无锁 SPSC 队列,粗测性能非常好,但在重负载和线程频繁调度切换的情况下,偶发出现读取到重复数据的情况。查了半天发现是无锁队列实现里对 head/tail 指针的 ABA 问题没有处理,CAS 成功但语义上已经过了一轮版本。
ABA 的经典解法是给指针加上版本号 / 序号。现在的多生产者多消费者队列大多已经内置了 seq 字段,选型时一定要仔细看实现,不要自己随手写一个 CAS 就上线。如果你只是需要一个简单的线程间消息传递,boost::lockfree::queue或者moodycamel::ConcurrentQueue都更靠谱,不要重复造轮子,尤其在安全攸关的环境里。
4.5 日志阻塞:调试日志竟成了事故元凶
有一次调试停车功能时,一开启日志,车就出现轻微顿挫;把日志关掉,现象立刻消失。起初怀疑是算法问题,后来发现是日志模块异步队列满了,业务线程在写日志时发生了阻塞,直接把控制周期拖垮了。这跟前面 spdlog 的注意点完全对应上。
把日志调试当成运行时的一部分来设计,而不是一个“外挂工具”。日志库和业务线程隔离,业务代码里只做内存写入,落盘交给独立线程;日志容量和队列深度要做一个流量估算。高吞吐场景下宁可丢弃部分日志,也不要让日志去阻塞控制路径。
4.6 运行库与工具链版本漂移
代码从一个环境搬到另一个环境最容易出问题的就是运行库版本。早期我在一个跨团队项目里,A 组负责的算法库用 GCC 9 编,B 组负责的控制模块用 GCC 11 编,cpp 二进制和 ABI 有关的问题层出不穷,一会儿是 symbol 找不到,一会儿是 std::string 大小不一致。
规范的做法是全链路统一编译器版本、统一标准库版本,最好直接通过容器或工具链镜像管理。车端部署包里也要把依赖的 so 库列成清单,做版本校验,避免拿着本机编译的包往板子上硬扔。热词里有“c++ 64位 fopen报安全错误”,这类问题本质是工具链层面对安全 API 的提示差异,换了个编译环境就冒出来,多留一个心,配好统一的 _CRT_SECURE_NO_WARNINGS 和编译选项,能省很多莫名其妙的排查功夫。
4.7 性能剖面:用数据说话而不是“感觉”
“这块慢,要不要优化一下?”这种讨论在项目里太常见了。我的态度一直是:先 profile,再优化。用perf record/annotate、gprof、火焰图、gdb的采样堆栈,把 CPU 占用分布拉出来,看 95% 的时间到底花在哪里,而不是靠猜。
我遇到过某模块大家都认为瓶颈是某个复杂算法,结果 profile 以后发现最耗时的竟然是字符串格式化 — 有个 debug 函数在每次迭代里构造了复杂字符串,根本没人注意。换一个思路,把格式化的开销移到分支外,整体提速 40%。做自动驾驶 C++ 开发,profile 能力和算法能力一样重要,甚至更重要:数据会告诉我们真相,感觉通常会骗人。
5. 给想入行的人一些实在建议
5.1 模拟题与真实工程的差别
不少同学问我,刷“冒泡排序 c++”“快速幂 c++”“C++八股文”这些东西能把自动驾驶 offer 拿下来吗?我的回答是:有帮助,但不是充分条件。面试官真正看你的是工程思维,比如缓冲区要不要预分配、析构会不会抛异常、两个线程共享一个变量会不会出问题、时间预算只剩 2 毫秒时你会怎么设计数据结构。
建议在刷题之外,多写一些工程性较强的 C++ 项目,比如一个带线程池的消息分发系统、一个支持序列化的数据缓存模块、一个能性能剖面的小型实时数据处理管线。这类项目能帮你在面试里聊到细节时真的有东西可说,而不是停在“我调用过 vector 和 map”的程度。
5.2 从小模块开始,做一个能跑的“小车”
与其一上来就想写一个完整的自动驾驶系统,不如先做一个最小闭环。比如买一台小车底盘,装一个摄像头,用 ROS 2 + C++ 写一个“感知到避障”的迷你管线:摄像头采集、图像预处理、简单的目标检测、决策和电机控制。别小看这个工程,它会逼你把驱动、线程、通信、时延、内存占用这些全串起来,比读十本书都管用。
我的经验是,能跑通一个最小闭环后,你会对 C++ 在“车”上到底扮演什么角色有非常具体的体感。然后你可以试着一步步替换更深的部分:把串行处理改成多线程,把裸指针改成智能指针,把锁改成无锁队列,把日志改成异步,把数据埋点接入 TDengine 做离线分析。每一步都是一次真实的工程技术升级。
5.3 语言只是门槛,系统思维才是护城河
最后说点掏心窝子的。C++ 在自动驾驶系统里之所以不可替代,说到底不是因为 C++ 这门语言有什么魔法,而是因为它承载的“确定性 + 性能 + 资源可控”正好匹配这个行业最硬核的需求。写 C++ 的人如果只停留在语法层面,那他写出来的代码跟写 Java 的没什么两样,根本无法胜任车辆实时系统的开发。
真正值钱的是系统级思维。你会不会从一帧图像的 DMA 地址开始,一路追踪到控制指令的 PWM 引脚;你能不能在设计模块的时候就把时延预算、内存预算、异常预算都留好;你能不能在一堆 dump 堆栈和日志里,快速定位到底是谁抢占了谁的 CPU 时间。这些能力不是背几条 C++ 语法就能获得的,需要大量真实的调试、压测、翻车、复盘。
就我自己的体会来说,C++ 和自动驾驶系统的组合,最大的魅力不在于“语言够底层”,而在于它逼着每一个开发者去做严谨的工程决策。你写的每一行代码,最终都会跑在一辆真实的、载着人的车上。每一次崩溃、每一毫秒的延迟,都可能被乘客感知到。这种压力和成就感,是其他很多软件开发方向体会不到的。
如果你正打算入行,我的建议很简单:先把 C++ 的基础打牢,然后把一辆小车跑起来,最后反复问自己“如果这个模块在车上每秒跑 50 次,它还能不能这么写”。想清楚这个问题,你离一个合格的自动驾驶 C++ 工程师就不远了。