3. C++ 基础回顾:指针与引用、智能指针、STL 容器与算法、面向对象设计模式在导航模块中的应用
做导航系统开发,C++ 是绕不开的坎。我见过不少同事,算法推得飞起,一写代码就崩。说白了,不是算法不行,是 C++ 基础没打牢。这一章,咱们就把导航模块里最常用的几个 C++ 特性过一遍。
3.1 指针与引用:别搞混了
指针和引用,看起来差不多,用起来天差地别。我刚开始做嵌入式时,就因为在回调函数里传了个空指针,导致整个导航线程挂掉。那次排查了一整天才找到原因。
| 特性 | 指针 | 引用 |
|---|---|---|
| 是否可为空 | 可以(nullptr) | 必须初始化,不能为空 |
| 重新绑定 | 可以指向不同对象 | 绑定后不能改变 |
| 内存占用 | 通常 4/8 字节 | 无额外开销(别名) |
| 适用场景 | 动态分配、可选参数 | 函数参数、操作符重载 |
3.2 智能指针:内存泄漏的终结者
导航模块里,传感器数据、地图图层、路径规划结果,动不动就 new 一堆对象。以前全靠手动 delete,漏一个就内存泄漏。后来全面改用智能指针,省心多了。
三种智能指针,各有各的脾气:
- std::unique_ptr:独占所有权。适合传感器驱动、单个资源。我习惯用它管理串口或 I2C 总线对象。
- std::shared_ptr:共享所有权。适合多个模块共同访问的地图数据。注意循环引用,用 weak_ptr 破环。
- std::weak_ptr:弱引用。观察者模式里常用,不增加引用计数。
// 导航模块中的典型用法
class SensorDriver {
public:
SensorDriver(const std::string& name) : name_(name) {}
void readData() { /* ... */ }
private:
std::string name_;
};
// 使用 unique_ptr 管理传感器
auto gps = std::make_unique<SensorDriver>("GPS");
auto imu = std::make_unique<SensorDriver>("IMU");
// 使用 shared_ptr 管理共享地图
std::shared_ptr<MapLayer> globalMap = std::make_shared<MapLayer>();
3.3 STL 容器与算法:选对工具,事半功倍
导航系统里,数据结构的选型直接影响性能。我见过有人用 vector 存路径点,结果频繁插入删除,性能惨不忍睹。你想想看,路径规划每帧要更新几百个点,用错容器就是灾难。
| 容器 | 特点 | 导航模块典型用途 |
|---|---|---|
| std::vector | 连续内存,随机访问快 | 存储路径点、传感器数据流 |
| std::list | 双向链表,插入删除快 | 动态障碍物列表(频繁增删) |
| std::map | 红黑树,有序键值对 | 路网节点索引、配置参数表 |
| std::unordered_map | 哈希表,O(1) 查找 | 快速查找地标、缓存最近轨迹 |
| std::priority_queue | 堆结构,自动排序 | A* 算法的开放列表 |
算法方面,STL 提供的 std::sort、std::find_if、std::transform 足够应付大部分场景。我个人习惯用 lambda 表达式配合算法,代码简洁又高效。
// 对路径点按距离排序
std::vector<Waypoint> waypoints = getWaypoints();
std::sort(waypoints.begin(), waypoints.end(),
[](const Waypoint& a, const Waypoint& b) {
return a.distance < b.distance;
});
// 查找最近的障碍物
auto it = std::min_element(obstacles.begin(), obstacles.end(),
[&robotPos](const Obstacle& obs) {
return distance(robotPos, obs.position);
});
reserve() 预分配 vector 空间。我曾在路径平滑模块里,因为没 reserve,导致每帧多次内存重分配,CPU 占用直接飙到 30%。加上 reserve 后降到 5%。
3.4 面向对象设计模式:单例与工厂
设计模式这东西,用好了是神器,用烂了是累赘。导航模块里,有两个模式我几乎每个项目都用。
3.4.1 单例模式:全局唯一的资源管理器
导航系统里,日志系统、配置管理器、线程池,这些全局只需要一份。单例模式正好合适。
class NavigationConfig {
public:
static NavigationConfig& getInstance() {
static NavigationConfig instance;
return instance;
}
double getParam(const std::string& key) {
// 从配置文件或参数服务器读取
return params_[key];
}
private:
NavigationConfig() = default;
~NavigationConfig() = default;
NavigationConfig(const NavigationConfig&) = delete;
NavigationConfig& operator=(const NavigationConfig&) = delete;
std::unordered_map<std::string, double> params_;
};
// 使用
double maxSpeed = NavigationConfig::getInstance().getParam("max_speed");
3.4.2 工厂模式:解耦对象创建
导航模块要支持多种传感器、多种路径规划算法、多种地图格式。如果每次新增一种就在主代码里加 if-else,维护起来会疯掉。工厂模式就是用来解决这个问题的。
// 抽象产品
class PathPlanner {
public:
virtual std::vector<Waypoint> plan(const Pose& start, const Pose& goal) = 0;
virtual ~PathPlanner() = default;
};
// 具体产品
class AStarPlanner : public PathPlanner { /* ... */ };
class DijkstraPlanner : public PathPlanner { /* ... */ };
class RRTPlanner : public PathPlanner { /* ... */ };
// 工厂
class PathPlannerFactory {
public:
static std::unique_ptr<PathPlanner> create(const std::string& type) {
if (type == "astar") return std::make_unique<AStarPlanner>();
if (type == "dijkstra") return std::make_unique<DijkstraPlanner>();
if (type == "rrt") return std::make_unique<RRTPlanner>();
return nullptr;
}
};
// 使用
auto planner = PathPlannerFactory::create(config.getPlannerType());
auto path = planner->plan(currentPose, targetPose);
这样做的好处很明显:新增一种规划算法,只需要写一个新类,然后在工厂里加一行。主流程代码完全不用动。
3.5 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作导航模块 C++ 开发的速查地图。
嗯,这一章的内容就到这里。指针和引用别搞混,智能指针用起来,STL 容器选对,设计模式别滥用。这些基础打牢了,后面讲导航算法实现的时候,你才能把精力放在逻辑上,而不是跟 C++ 语法较劲。