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>();
我曾经踩过的坑:在回调函数里捕获 shared_ptr 的 this 指针,导致对象销毁后回调还在执行。后来改用 enable_shared_from_this 才解决。记住:不要在 lambda 里裸传 this。

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");
注意:C++11 以后的局部静态变量初始化是线程安全的,不用再加双重检查锁。但如果你要在构造函数里做复杂初始化,还是得小心竞态条件。

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++ 开发的速查地图。

导航模块 C++ 核心知识体系 C++ 基础回顾 指针与引用 智能指针 STL 容器与算法 设计模式 可为空 vs 不可为空 重新绑定 vs 别名 函数参数传递 unique_ptr shared_ptr weak_ptr vector / list / map sort / find_if lambda + 算法 单例模式 工厂模式 解耦对象创建 导航模块开发核心原则 1. 优先使用引用,必要时用指针 2. 智能指针管理动态资源,避免裸 new/delete 3. 选对容器,用对算法,性能翻倍

嗯,这一章的内容就到这里。指针和引用别搞混,智能指针用起来,STL 容器选对,设计模式别滥用。这些基础打牢了,后面讲导航算法实现的时候,你才能把精力放在逻辑上,而不是跟 C++ 语法较劲。