跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 策略模式:基于 std::function 的动态行为切换

综述由AI生成探讨 C++ 中的策略模式实现。对比了传统虚函数继承导致的类膨胀和行为固化问题,提出使用 std::function 进行策略注入的方案。通过 Hero 类示例,演示了如何通过普通函数、Lambda 表达式及 std::bind 绑定成员函数,在运行时动态切换攻击行为,提高了代码的灵活性和可维护性。

星辰大海发布于 2026/3/27更新于 2026/5/2326 浏览

场景设定

我们需要设计一个 Hero(英雄)类,他有一个 Attack(攻击)的行为。

英雄可以使用'剑'攻击,也可以用'火球'攻击,甚至可以'空手'攻击。

1. 传统的做法(虚函数 + 继承)

在传统 OOP 中,如果不使用策略模式,你可能会觉得:既然攻击方式不同,那就派生出不同的子类吧。

// 基类
class Hero {
public:
    virtual void Attack() {
        cout << "空手攻击!" << endl;
    }
};

// 派生类:剑士
class Swordsman : public Hero {
public:
    void Attack() override {
        cout << "挥剑斩击!" << endl;
    }
};

// 派生类:法师
class Mage : public Hero {
public:
    void Attack() override {
        cout << "发射火球!" << endl;
    }
};

这种写法的痛点:

  • 死板:一旦你创建了一个 Swordsman 对象,他就永远只能挥剑。如果他捡到一根魔杖想变成法师怎么办?你没法把一个 Swordsman 对象直接变成 Mage 对象。
  • 类爆炸:如果你有 100 种武器,你难道要写 100 个 Hero 的子类吗?

2. 新写法(std::function + 策略注入)

现在我们用你提到的方法。Hero 不再有子类,它只是一个'容器',它的 Attack 行为由一个 std::function 变量决定。

我们可以把这个变量理解为英雄装备的'技能卡槽'。

A. 定义 Hero 类

#include <iostream>
#include <functional>

class Hero; // 前置声明

// 定义接口类型:这是一个'函数签名',它接受一个 Hero 引用,没有返回值
using AttackStrategy = std::function<void(const Hero&)>;

// 默认的攻击方式(普通函数)
void DefaultPunch(const Hero& h) {
    std::cout << "英雄使用了:普通拳击 (默认)" << std::endl;
}

class Hero {
public:
    // 构造函数:默认装备'普通拳击'技能卡
    Hero(AttackStrategy strategy = DefaultPunch) : attack_skill(strategy) {}

    // 公开的非虚函数接口
    void DoAttack() {
        std::cout << "[准备动作] 英雄深吸一口气..." << std::endl;
        // 核心:调用当前装备的技能卡
        attack_skill(*this);
        std::cout << "[结束动作] 攻击完成。" << std::endl;
    }

    // 关键点:我们可以随时更换技能卡!
    void ChangeWeapon(AttackStrategy new_strategy) {
        attack_skill = new_strategy;
    }

private:
    AttackStrategy attack_skill; // 这里保存了具体的'怎么打'
};

B. 三种不同的实现(举例说明)

现在,可以通过给 attack_skill 赋值不同的内容,让同一个 Hero 对象表现出完全不同的样子。

例子 1:普通函数(最简单的实现)

// 一个普通的全局函数
void UseSword(const Hero& h) {
    std::cout << ">>> EXCALIBUR! (使用圣剑攻击)" << std::endl;
}

// 使用:
Hero h1(UseSword);
h1.DoAttack();
// 输出:
// [准备动作] ...
// >>> EXCALIBUR! ...

例子 2:Lambda 表达式(最方便的实现)

有时候我们不想专门写个函数,只想临时定义一个招式,比如'扔石头'。

// 使用 Lambda 直接定义逻辑
Hero h2([](const Hero& h) {
    std::cout << ">>> 扔出了一块石头!(Lambda 实现)" << std::endl;
});
h2.DoAttack();

例子 3:成员函数 + std::bind(最复杂的实现,也是你困惑的地方)

假设我们有一个复杂的 MagicBook(魔法书)类,里面存了很多法术。这些法术是 MagicBook 的成员函数,而不是全局函数。

class MagicBook {
public:
    void CastFireball(const Hero& h) {
        std::cout << ">>> 这里的魔法书:发射大火球!" << std::endl;
    }
    void CastIceStorm(const Hero& h) {
        std::cout << ">>> 这里的魔法书:召唤暴风雪!" << std::endl;
    }
};

现在问题来了:MagicBook::CastFireball 也是一个函数,但它的参数其实有两个:(MagicBook* this, const Hero& h)。

而我们的 std::function 只接受一个参数 (const Hero& h)。签名不匹配!

这就好比插头(函数)是三孔的,但插座(std::function)是两孔的。

std::bind 就是那个转接头。它把 MagicBook 的对象实例(this)直接'焊死'在函数上,让它对外看起来只剩下一个参数。

int main() {
    MagicBook myBook; // 我有一本魔法书对象
    // 创建一个英雄
    Hero h3;

    // 1. 英雄想用魔法书里的火球术
    // 这里的 bind 意思:把 myBook 绑定到 CastFireball 的第一个参数(this) 上
    // std::placeholders::_1 表示:Hero 参数留空,等调用的时候再填
    h3.ChangeWeapon(std::bind(&MagicBook::CastFireball, &myBook, std::placeholders::_1));
    h3.DoAttack(); // 输出:发射大火球!

    // 2. 英雄想换成冰风暴
    h3.ChangeWeapon(std::bind(&MagicBook::CastIceStorm, &myBook, std::placeholders::_1));
    h3.DoAttack(); // 输出:召唤暴风雪!

    return 0;
}

总结对比

特性传统虚函数写法std::function 写法 (策略模式)
实现方式class Swordsman : public Heroh.ChangeWeapon(UseSword)
切换行为不可能 (你是剑士就一辈子是剑士)极易 (运行时随时调用 ChangeWeapon)
扩展性需新增子类无需新增类,仅修改策略

目录

  1. 场景设定
  2. 1. 传统的做法(虚函数 + 继承)
  3. 2. 新写法(std::function + 策略注入)
  4. A. 定义 Hero 类
  5. B. 三种不同的实现(举例说明)
  6. 总结对比
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Go Map 底层原理深度解析
  • Kimi K2.5 开源权重多模态旗舰大模型详解
  • RCTF 2025 Web 部分解题思路与漏洞分析
  • Hibernate HQL 核心语法详解与实战技巧
  • 基于 OpenClaw 与优云智算的公众号 AI 自动化创作发布流程
  • 基于 Simulink 的协作机器人末端执行器抓取控制仿真
  • MySQL ON DUPLICATE KEY UPDATE 实现存在更新不存在插入
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具实践
  • C++ 网络编程实战:基于 TCP 协议的简易计算器
  • Ubuntu 系统下 Python 连接 KingbaseES 数据库实现增删改查
  • 基于 Spring AI 和 Claude 构建企业智能客服系统:架构与实践
  • Java 多态详解:从向上转型到向下转型与动态绑定
  • Neo4j 跨平台安装与基础配置指南(Windows/Mac/Linux)
  • C 语言与 C++ 的关系详解
  • Llama-Factory 支持 Flash Attention 吗?训练加速配置详解
  • 基于多 AI 模型并行的内容生成与对比分析工作流
  • 渐进式 Web 应用开发实例:核心技术与实战
  • 华为 OD 机试:补种未成活胡杨
  • 命令行大模型上下文协议 MCP 工具 MCPHost 使用指南
  • 阿里推出 AI 编程插件 Qoder,JetBrains 集成体验一周评测

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online