7.3小学期基础语法记录:结构化绑定与行为树

🙂 std::bind() 结构化绑定

std::bind 是 C++11 引入的一个函数适配器,用于绑定函数的一部分参数重新排列参数顺序,从而生成一个新的可调用对象(callable object)。它属于头文件 <functional>,与 std::functionstd::ref 等一同使用非常灵活。


🧠 本质作用

将一个函数(普通函数、成员函数、函数对象或 lambda)“部分调用”,返回一个新的可调用对象。


🧱 头文件

#include <functional>

✅ 基本语法

auto new_func = std::bind(callable, arg1, arg2, ..., argN);

其中:

  • callable 是原始函数或函数对象;
  • arg1~argN 是实际传入参数,可以是具体的值、std::placeholders::_1 等占位符;
  • 返回一个新的函数对象 new_func,可以用类似 lambda 的方式调用。

📌 示例:绑定普通函数

#include <iostream>
#include <functional>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    // 固定第一个参数为 10,返回一个只需要一个参数的函数
    auto add10 = bind(add, 10, std::placeholders::_1);
    cout << add10(5) << endl;  // 输出 15
}

🔍 解释:

  • std::placeholders::_1 表示将来调用时传入的第一个参数;
  • bind(add, 10, _1) 意味着调用 add(10, x),x 是调用 add10(x) 时传入的值。

💡 示例:绑定成员函数

#include <iostream>
#include <functional>
using namespace std;

struct Printer {
    void print(const string& msg) {
        cout << "Printing: " << msg << endl;
    }
};

int main() {
    Printer p;
    // bind 成员函数时,第一个参数要绑定对象或对象指针
    auto printer = bind(&Printer::print, &p, std::placeholders::_1);
    printer("Hello");  // 输出:Printing: Hello
}

🧩 参数重排序

int sub(int a, int b) {
    return a - b;
}

int main() {
    auto reverse_sub = bind(sub, std::placeholders::_2, std::placeholders::_1);
    cout << reverse_sub(3, 10) << endl; // 结果:7,即sub(10, 3)
}

🤯 结合 std::function

std::function<int(int)> f = bind(add, 100, std::placeholders::_1);
cout << f(23);  // 输出 123

📦 与 std::ref 一起用(避免值拷贝)

void update(int& x, int val) {
    x += val;
}

int main() {
    int a = 10;
    auto updater = bind(update, std::ref(a), std::placeholders::_1);
    updater(5);
    cout << a << endl;  // 输出 15
}

不加 std::ref 的话,a 会被按值复制,无法修改原始变量。


🛠️ 用途总结

用途 示例
固定部分参数(偏函数应用) bind(f, 10, _1)
改变参数顺序 bind(f, _2, _1)
延迟执行 把函数变成对象存储、传参,稍后执行
std::function 配合使用 用于泛型容器、回调等
std::ref 搭配避免拷贝 修改引用变量

⚠️ 注意事项

  1. 占位符必须使用 std::placeholders::_1, _2, ...,不能自定义。
  2. 当使用成员函数时,必须传入对象或对象指针。
  3. std::bind 可以实现的功能大多数用 lambda 更直观,C++14 以后建议优先用 lambda。
  4. bind 的参数是值拷贝,除非使用 std::ref

✅ 何时使用 std::bind

✅ 适合用 bind 的场景 ❌ 不推荐用 bind 的场景
需要在 C++11 中实现部分函数应用 C++14/17/20 可以直接使用 lambda 更清晰
将成员函数与某个对象绑定作为回调 lambda 更容易维护与调试
参数顺序需要调整 lambda 更明确可读

🔚 总结一句话

std::bind 是一个强大但在现代 C++ 中略显冗长的工具,它让你可以把函数变成你想要的“函数”,但 lambda 表达式通常是更现代、更清晰的替代方案。


⏫ 关于std::bind 的高级用法

std::bind 本质上是一个通用的函数适配器,功能覆盖很多高级使用场景。我们来继续深入。

🔍 一、常见高级用法汇总

用法类型 描述
部分参数绑定(偏函数应用) 固定部分参数生成新函数
参数重排 调换调用顺序
与成员函数 + 对象结合使用 将成员函数转为独立函数对象
std::ref / std::cref 避免不必要的拷贝(传引用或 const 引用)
返回函数对象 bind 返回可调用对象,适合配合 STL 算法、回调、事件系统等
用于多参数函数绑定多个位置 bind(f, _2, 3, _1, 42) 等,灵活绑定不同参数位置

✅ 二、更复杂实例讲解


1️⃣ 绑定多个位置+混合值与占位符

int func(int a, int b, int c, int d) {
    return a + b * c - d;
}

auto bound = std::bind(func, std::placeholders::_2, 2, std::placeholders::_1, 10);

int result = bound(3, 4);  // 相当于 func(4, 2, 3, 10) → 4 + 2*3 - 10 = 0

2️⃣ 与 STL 算法结合

#include <vector>
#include <algorithm>
#include <functional>
#include <iostream>

bool greater_than(int x, int y) {
    return x > y;
}

int main() {
    std::vector<int> v = {5, 2, 8, 1};

    // 生成一个降序排序函数(固定第一个参数)
    std::sort(v.begin(), v.end(), std::bind(greater_than, std::placeholders::_2, std::placeholders::_1));

    for (int i : v) std::cout << i << ' ';  // 输出:8 5 2 1
}

std::sortstd::for_eachstd::transform 搭配使用时非常灵活。


3️⃣ 与成员函数指针和成员变量搭配

struct Person {
    std::string name;
    int age;

    void sayHi(const std::string& msg) const {
        std::cout << name << " says: " << msg << '\n';
    }
};

int main() {
    Person p{"Alice", 20};

    // 调用成员函数
    auto say = std::bind(&Person::sayHi, &p, std::placeholders::_1);
    say("Hello");

    // 访问成员变量(注意返回的是引用)
    auto getAge = std::bind(&Person::age, &p);
    std::cout << getAge() << std::endl;  // 输出 20
}

4️⃣ 配合 std::function 作为通用回调

std::function<void(int)> callback;

void handle_event(int event_id) {
    std::cout << "Handling event: " << event_id << std::endl;
}

callback = std::bind(handle_event, std::placeholders::_1);
callback(42);

5️⃣ 配合多线程

#include <thread>

void task(int id, const std::string& name) {
    std::cout << "Thread " << id << ": " << name << std::endl;
}

int main() {
    std::thread t(std::bind(task, 1, "Worker"));
    t.join();
}

🔁 三、std::bind vs lambda 表达式

特性 std::bind lambda 表达式
参数顺序重排 支持,占位符 _1, _2 lambda 更直观但不支持“自动”重排
绑定成员函数 支持 支持,但写法更繁琐
捕获引用 需搭配 std::ref & 捕获即可
可读性 对初学者不太友好 lambda 更清晰
复杂重排/组合 优势明显 写 lambda 可能冗长

🚫 四、std::bind 的几个坑

✅ 传引用时注意引用失效

int a = 10;
auto f = std::bind([](int& x){ x = 99; }, a);  // 拷贝了 a,不会修改原变量
f();
std::cout << a << std::endl;  // 输出仍是 10

// 正确做法
auto f2 = std::bind([](int& x){ x = 99; }, std::ref(a));
f2();
std::cout << a << std::endl;  // 输出 99

✅ 使用成员函数时对象生命周期要注意

auto f = std::bind(&Person::sayHi, Person(), _1);  // 临时对象立即销毁
f("hello");  // UB!悬空对象

📚 五、总结

分类 例子
普通函数绑定参数 bind(add, 1, _1)
成员函数绑定对象 bind(&Class::method, &obj, _1)
成员变量访问器 bind(&Class::var, &obj)
参数重排 bind(f, _2, _1)
STL算法适配 sort(v.begin(), v.end(), bind(...))
多线程任务封装 thread(bind(func, args...))
回调函数/事件系统封装 std::function<void()> cb = bind(...)
延迟执行或惰性计算 auto lazy = bind(...)

💠 std::bind 与状态模式

非常好,这是进入 C++ 高阶编程的关键。我们现在讲讲 std::bind状态模式事件驱动框架回调系统 等工程应用中的高级封装方式。

这些场景中,bind 最重要的作用是:

把行为(函数)抽象成参数 —— 也就是"函数指针 + 上下文 + 参数封装"。


🧩 一、在状态模式中的应用

✅ 场景描述

状态模式需要对象根据当前状态调用不同处理函数,而且这些函数往往有不同的“上下文”参数。我们希望把这些函数“封装”成行为对象,并在状态转移时统一管理。


🔨 示例:简化状态切换绑定函数

#include <iostream>
#include <functional>
#include <unordered_map>
#include <string>

using namespace std;

class Robot {
public:
    void idle() {
        cout << "[State] Idle...\n";
    }
    void walking(int speed) {
        cout << "[State] Walking at speed " << speed << "\n";
    }
    void charging() {
        cout << "[State] Charging...\n";
    }
};

int main() {
    Robot r;

    // 将状态行为封装为函数对象
    unordered_map<string, function<void()>> stateActions;
    stateActions["IDLE"]     = bind(&Robot::idle, &r);
    stateActions["WALKING"]  = bind(&Robot::walking, &r, 5);  // 固定 speed
    stateActions["CHARGING"] = bind(&Robot::charging, &r);

    // 状态调度模拟
    vector<string> state_sequence = {"IDLE", "WALKING", "CHARGING"};
    for (auto& state : state_sequence) {
        stateActions[state]();  // 统一调用接口
    }
}

✨ 优点:

  • 所有行为都封装成 function<void()>
  • 不需要区分传参方式,也无需用继承或虚函数
  • bind 把成员函数 + 参数“打包”成标准接口

🌀 二、在回调/事件框架中的封装

你可以将 std::bind 用于:

  • 网络/IO事件触发时的回调
  • UI/交互信号的注册
  • 控制器(如ROS)策略行为的回调链

🔔 示例:事件注册系统

#include <iostream>
#include <unordered_map>
#include <functional>

using namespace std;

class EventSystem {
    unordered_map<string, function<void(int)>> handlers;
public:
    void registerHandler(const string& name, function<void(int)> f) {
        handlers[name] = f;
    }

    void trigger(const string& name, int code) {
        if (handlers.count(name)) handlers[name](code);
        else cout << "No handler for event: " << name << endl;
    }
};

class Controller {
public:
    void onSensorEvent(int value) {
        cout << "[Sensor] Value: " << value << endl;
    }

    void onError(int code) {
        cout << "[Error] Code: " << code << endl;
    }
};

int main() {
    EventSystem es;
    Controller ctrl;

    es.registerHandler("sensor", bind(&Controller::onSensorEvent, &ctrl, std::placeholders::_1));
    es.registerHandler("error", bind(&Controller::onError, &ctrl, std::placeholders::_1));

    es.trigger("sensor", 42);
    es.trigger("error", 404);
}

🧠 深层理解:为什么用 bind 优于裸函数指针?

裸函数指针有几个致命弱点:

  • 不支持成员函数(必须是静态函数)
  • 无法封装上下文(比如对象引用)
  • 不能传参或调整参数格式
  • 无法延迟执行、不能组合

std::bind 搭配 std::function 做到以下关键特性:

能力 bind 是否支持 说明
封装对象上下文 成员函数自动绑定对象指针
延迟执行 不立即调用,只创建 callable
参数预绑定 比如提前绑定固定值
占位符支持 _1, _2 等支持动态注入
统一接口 封装成 function<sig>

🚀 三、在行为树 / 机器人策略模块中的应用

比如你在开发决策树/行为树/状态机时,行为节点(比如“转向”、“开火”、“避障”)都可以用 bind 绑定后注册到节点里。

class ActionNode {
public:
    function<bool()> action;

    ActionNode(function<bool()> func) : action(func) {}
    bool execute() {
        return action();
    }
};

class Agent {
public:
    bool move() {
        cout << "Moving..." << endl;
        return true;
    }
    bool avoidObstacle() {
        cout << "Avoiding obstacle!" << endl;
        return false;
    }
};

int main() {
    Agent agent;

    ActionNode moveNode(bind(&Agent::move, &agent));
    ActionNode avoidNode(bind(&Agent::avoidObstacle, &agent));

    moveNode.execute();
    avoidNode.execute();
}

🧾 总结:什么时候应该用 bind 来封装行为?

场景类型 是否推荐使用 bind 说明
成员函数注册 简化对象 + 成员函数调用
状态模式行为映射 使用 map<string, function> 管理状态
回调系统 参数 + 上下文封装为统一接口
参数部分绑定 用于生成简化接口、用于事件系统
C++14 以上 🟡 推荐用 lambda 更简洁,但可读性差时仍推荐 bind

🏖️ 行为调度器(BehaviorManager)的简单Demo

好的,Eigh18n,我们现在设计一个完整的机器人行为调度系统架构草图,结合你正在学习的“视觉导航”、“策略规划”和“决策树”,我们将重点使用 std::bindstd::function 来搭建一个灵活可扩展的行为系统框架,适合后续拓展为行为树、状态机甚至 ROS 行为插件。


🎯 系统目标

设计一个行为调度器(BehaviorManager),可注册多个“行为节点”,每个节点封装一个动作函数(如移动、转向、避障、执行任务等),并支持运行、切换、失败回退等机制。


📐 模块架构图

+-----------------------+
|   BehaviorManager     | <-- 注册、切换、执行行为节点
+-----------------------+
| + register(name, fn)  |
| + run(name)           |
+-----------------------+
           |
           v
+-----------------------+
|     BehaviorNode      | <-- 每个行为:一个函数对象 + 状态
+-----------------------+
| + execute() -> bool   |
+-----------------------+

行为函数由 RobotController 提供
+-----------------------+
|   RobotController     |
+-----------------------+
| + moveTo(x, y)        |
| + avoidObstacle()     |
| + followLine()        |
+-----------------------+

🧱 Step-by-step 实现

1️⃣ 定义行为节点(BehaviorNode)

#include <functional>
#include <string>

class BehaviorNode {
public:
    using Action = std::function<bool()>;

    BehaviorNode(Action act, const std::string& name = "")
        : action(std::move(act)), node_name(name) {}

    bool execute() {
        return action();  // 返回 true 表示成功,false 表示失败
    }

    std::string name() const {
        return node_name;
    }

private:
    Action action;
    std::string node_name;
};

2️⃣ 定义行为管理器(BehaviorManager)

#include <unordered_map>
#include <iostream>

class BehaviorManager {
public:
    void registerBehavior(const std::string& name, BehaviorNode::Action act) {
        behaviors[name] = BehaviorNode(act, name);
    }

    bool run(const std::string& name) {
        if (behaviors.count(name)) {
            std::cout << "[Executing] " << name << std::endl;
            bool result = behaviors[name].execute();
            std::cout << "→ Result: " << (result ? "Success" : "Failure") << "\n";
            return result;
        }
        std::cerr << "Behavior not found: " << name << std::endl;
        return false;
    }

private:
    std::unordered_map<std::string, BehaviorNode> behaviors;
};

3️⃣ 定义机器人控制器(RobotController)

#include <iostream>

class RobotController {
public:
    bool moveTo(int x, int y) {
        std::cout << "Moving to (" << x << "," << y << ")\n";
        return true;
    }

    bool avoidObstacle() {
        std::cout << "Obstacle detected, avoiding...\n";
        return true;
    }

    bool followLine() {
        std::cout << "Following line using vision...\n";
        return true;
    }

    bool failExample() {
        std::cout << "Trying something... Failed\n";
        return false;
    }
};

4️⃣ 注册行为(使用 std::bind

int main() {
    RobotController robot;
    BehaviorManager bm;

    using namespace std::placeholders;

    // 注册各种行为
    bm.registerBehavior("moveToTarget", std::bind(&RobotController::moveTo, &robot, 10, 20));
    bm.registerBehavior("avoid", std::bind(&RobotController::avoidObstacle, &robot));
    bm.registerBehavior("lineFollow", std::bind(&RobotController::followLine, &robot));
    bm.registerBehavior("failTest", std::bind(&RobotController::failExample, &robot));

    // 执行行为
    bm.run("lineFollow");
    bm.run("moveToTarget");
    bm.run("avoid");
    bm.run("failTest");
}

✅ 输出示例

[Executing] lineFollow
Following line using vision...
→ Result: Success

[Executing] moveToTarget
Moving to (10,20)
→ Result: Success

[Executing] avoid
Obstacle detected, avoiding...
→ Result: Success

[Executing] failTest
Trying something... Failed
→ Result: Failure

🧠 可扩展性设计方向

扩展目标 说明
✅ 状态判断/条件行为 可加入 condition 参数:if (check()) then run()
✅ 行为树/序列节点 可将多个 BehaviorNode 组合为 SequenceNode/FallbackNode
✅ 行为优先级调度 给每个节点设置 priority 字段,设计调度器策略
✅ 异步/延迟行为执行 Action 扩展为异步:返回 std::future<bool>、支持超时控制等
✅ ROS 节点整合 bind(&Class::ros_callback, this, ...) 注册行为、事件、回调等

📘 总结

你现在拥有了一个完整的、可组合的行为框架基础:

  • std::bind 灵活注册成员函数(带参数)为标准行为对象;
  • std::function 封装成通用回调格式;
  • 利用 unordered_map<string, function> 构建行为调度系统;
  • 后续你可以加入状态判断、行为树节点等结构来拓展智能决策系统。

📡 行为树(Behavior Tree)的简单Demo

我们现在在上一节行为系统的基础上,进一步构建一个轻量但完整的行为树(Behavior Tree)框架,适合机器人导航/策略控制/视觉任务调度等场景,注重模块组合、扩展性与执行逻辑清晰。


🧠 本轮目标:构建行为树系统

✅ 功能点:

  1. 行为节点(ActionNode):封装基本行为(如走路、避障)
  2. 组合节点(Composite):支持:
    • 顺序节点(Sequence):所有子节点都成功才成功
    • 选择节点(Selector):任一子节点成功就成功
  3. 条件节点(Condition):可加入判断逻辑,如视觉检测是否通过
  4. 返回值标准化:所有节点统一返回 enum class Status { SUCCESS, FAILURE, RUNNING }

🧱 一、通用结构定义

#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <memory>

enum class Status { SUCCESS, FAILURE, RUNNING };

class BehaviorNode {
public:
    virtual ~BehaviorNode() = default;
    virtual Status execute() = 0;
    virtual std::string name() const = 0;
};

🔨 二、实现基本行为节点(ActionNode)

class ActionNode : public BehaviorNode {
public:
    using Action = std::function<Status()>;

    ActionNode(std::string n, Action act) : node_name(std::move(n)), action(std::move(act)) {}

    Status execute() override {
        std::cout << "[Action] " << node_name << "...\n";
        return action();
    }

    std::string name() const override { return node_name; }

private:
    std::string node_name;
    Action action;
};

🧠 三、实现组合节点(Sequence 和 Selector)

class CompositeNode : public BehaviorNode {
protected:
    std::vector<std::shared_ptr<BehaviorNode>> children;
    std::string node_name;

public:
    CompositeNode(std::string n) : node_name(std::move(n)) {}

    void addChild(std::shared_ptr<BehaviorNode> child) {
        children.push_back(std::move(child));
    }

    std::string name() const override { return node_name; }
};

class SequenceNode : public CompositeNode {
public:
    using CompositeNode::CompositeNode;

    Status execute() override {
        std::cout << "[Sequence] " << node_name << " Start\n";
        for (auto& child : children) {
            Status status = child->execute();
            if (status != Status::SUCCESS)
                return status;
        }
        return Status::SUCCESS;
    }
};

class SelectorNode : public CompositeNode {
public:
    using CompositeNode::CompositeNode;

    Status execute() override {
        std::cout << "[Selector] " << node_name << " Start\n";
        for (auto& child : children) {
            Status status = child->execute();
            if (status == Status::SUCCESS)
                return Status::SUCCESS;
        }
        return Status::FAILURE;
    }
};

🔍 四、实现条件判断节点(ConditionNode)

class ConditionNode : public BehaviorNode {
public:
    using Condition = std::function<bool()>;

    ConditionNode(std::string n, Condition cond)
        : node_name(std::move(n)), condition(std::move(cond)) {}

    Status execute() override {
        std::cout << "[Condition] " << node_name << "\n";
        return condition() ? Status::SUCCESS : Status::FAILURE;
    }

    std::string name() const override { return node_name; }

private:
    std::string node_name;
    Condition condition;
};

🤖 五、机器人控制逻辑封装(RobotController)

class RobotController {
public:
    bool obstacleDetected = false;

    Status moveTo(int x, int y) {
        std::cout << "→ Moving to (" << x << "," << y << ")\n";
        return Status::SUCCESS;
    }

    Status avoid() {
        std::cout << "→ Avoiding obstacle!\n";
        return Status::SUCCESS;
    }

    Status detectObstacle() {
        std::cout << "→ Checking for obstacle: " << (obstacleDetected ? "YES" : "NO") << "\n";
        return obstacleDetected ? Status::SUCCESS : Status::FAILURE;
    }
};

🧪 六、行为树构建 & 执行

int main() {
    RobotController robot;
    using namespace std::placeholders;

    // 创建节点
    auto checkObstacle = std::make_shared<ConditionNode>("IsObstacle",
        std::bind(&RobotController::detectObstacle, &robot));

    auto avoid = std::make_shared<ActionNode>("Avoid",
        std::bind(&RobotController::avoid, &robot));

    auto moveToTarget = std::make_shared<ActionNode>("MoveToTarget",
        std::bind(&RobotController::moveTo, &robot, 10, 20));

    // 构建 Selector(如果有障碍物则避障,否则继续)
    auto root = std::make_shared<SelectorNode>("RootSelector");

    auto obstacleBranch = std::make_shared<SequenceNode>("ObstacleHandling");
    obstacleBranch->addChild(checkObstacle);
    obstacleBranch->addChild(avoid);

    root->addChild(obstacleBranch);
    root->addChild(moveToTarget);  // fallback plan

    // --- 情况一:无障碍 ---
    std::cout << "\n--- No Obstacle ---\n";
    robot.obstacleDetected = false;
    root->execute();

    // --- 情况二:有障碍 ---
    std::cout << "\n--- Obstacle Detected ---\n";
    robot.obstacleDetected = true;
    root->execute();
}

🧾 输出结果示意

--- No Obstacle ---
[Selector] RootSelector Start
[Sequence] ObstacleHandling Start
[Condition] IsObstacle
→ Checking for obstacle: NO
[Action] MoveToTarget...
→ Moving to (10,20)

--- Obstacle Detected ---
[Selector] RootSelector Start
[Sequence] ObstacleHandling Start
[Condition] IsObstacle
→ Checking for obstacle: YES
[Action] Avoid...
→ Avoiding obstacle!

📘 总结设计亮点

功能 设计说明
行为封装 使用 std::bind 将行为与上下文、参数统一封装为 std::function
统一返回值 所有节点返回 Status(支持 SUCCESS / FAILURE / RUNNING)
可组合性强 Sequence / Selector / Condition 支持任意嵌套组合
易于拓展 可继续添加 LoopNode、ParallelNode、DelayNode 等节点类型

🚀 下一步可扩展方向

模块 功能描述
✅ 并发节点 多个子节点并行执行(适合视觉检测 + 导航同时进行)
✅ Loop 节点 持续尝试直到成功 / 失败次数限制
✅ 黑板(Blackboard) 用于行为树之间共享信息(如 ROS 的行为树库一样)
✅ 异步行为 加入延时/等待/异步检测(返回 RUNNING,状态轮询)
✅ XML 配置解析 从 XML/YAML 构建行为树(可以模仿 behavior_tree_cpp_v3

posted @ 2025-07-03 21:29  十八Eigh18n  阅读(24)  评论(0)    收藏  举报