🏦死对头的崛起
上周SigmaX好不容易重回正轨,正在规划第一个盈利季度的工作,这周一开局就遇到大麻烦。
某天下午,公司的QR Alice冲进黑犬办公室:
“老板!我们的动量策略今天完全失效了!市场上有人比我们快了至少 50ms,我们的单子还没发出去,机会就没了!”
QD Bob也气冲冲地跑来,拿着电脑给黑犬看系统日志:
“老板,我看出问题了。我们现在用的虚函数多态,每次策略执行都要查虚表,光这个开销就有几十纳秒。高频交易场景下,这个延迟是致命的!”
黑犬皱眉调出监控系统,发现原来是隔壁金融小镇的死对头——海归高材生”白猫”才成立的量化冲基金“AlphaZ”突然杀出来,并且他家的订单总是要早一些到交易所。
可恶🤬,这能忍吗?敢抢我的钱!黑犬光速拉着QD组的Tech lead Carol开会研究,很快决定了用CRTP静态多态技术重构公司的基本策略类:
// 旧方案 - 虚函数多态,参考上周的WorkStrategy.hpp
/**
* CRTP 策略基类 - 零虚函数开销的策略模式
*/
template<typename Derived>
class StrategyBase {
public:
// CRTP 接口
void execute(const std::string& employee_name) const {
std::cout << " [策略执行] " << name() << "\n";
static_cast<const Derived*>(this)->execute_impl(employee_name);
}
constexpr std::string_view name() const {
return Derived::strategy_name;
}
void log_execution() const {
std::cout << " [" << name() << "] 策略开始执行\n";
}
};通过CRTP,黑犬成功消除了虚函数调用的开销,策略执行速度提升了40%,成功抢回了一些单子,但AlphaZ还是领先不少,这让黑犬很不爽。
🚸风控盲区
周二,刚刚才解决性能问题,黑犬又接到风控部的经理Eva的投诉:
“狗哥,我发现一个严重问题!昨天有 3 笔订单的 ID 和员工 ID 搞混了,系统把订单 ID 0991 当成了员工 ID 0991,查询时返回了完全错误的数据!幸好我人工复核发现了,不然亏大了!”
好家伙,人力管理系统不是黑犬自己负责的吗🫣?黑犬马上检查员工管理系统源码,发现后来员工数量增加后虽然增加了员工ID区分员工,但是处都是 std::string 和 int 做 ID,完全没有类型区分:
// 老代码 - 类型不安全
Employee* find_employee(int id); // 员工 ID
Order* find_order(int id); // 订单 ID
// 问题:可以把订单 ID 传给 find_employee!黑犬先给Eva道歉,然后立刻召集QD组的Bob和Carol开会,决定用强类型ID系统来重构员工和订单ID:
/**
* 强类型 ID 模板 - 类型安全的 ID 系统
* 使用 Tag 类型区分不同的 ID 类型,防止混淆
*/
template<typename Tag, typename ValueType = uint64_t>
class StrongID {
private:
ValueType value;
public:
// 只能显式构造
constexpr explicit StrongID(ValueType val = 0) noexcept : value(val) {}
constexpr ValueType get() const noexcept { return value; }
constexpr bool is_valid() const noexcept { return value != 0; }
// 比较运算符 (C++20)
constexpr bool operator==(const StrongID& other) const noexcept = default;
constexpr bool operator!=(const StrongID& other) const noexcept = default;
constexpr bool operator<(const StrongID& other) const noexcept {
return value < other.value;
}
// 支持哈希,可用于 unordered_map
struct Hash {
size_t operator()(const StrongID& id) const noexcept {
return std::hash<ValueType>{}(id.value);
}
};
};
// 定义不同类型的 ID Tag
struct EmployeeTag {};
struct OrderTag {};
struct StrategyTag {};
// 具体的 ID 类型
using EmployeeID = StrongID<EmployeeTag>;
using OrderID = StrongID<OrderTag>;
using StrategyID = StrongID<StrategyTag>;这样就能在编译期防止ID混淆,避免类似错误再次发生,还好Eva发现的及时,不然损失可就大了,金融系统中的类型混淆可是灾难性的, 黑犬默默掏出小本本记下下个月给Eva多发点奖金。
🦹又被监管盯上了
周三的晚上黑犬忙了半天刚刚要回家,就被合规官Henry叫住了:
“老板,监管部门要求我们记录每个员工的所有操作日志、任务耗时指标,还要有权限控制。但如果我去修改每个员工类,要改 7 个文件,太容易出 bug 了!”
黑犬一听这话就头大,这不就是典型的横切关注点问题吗?如果每个员工类都要改,那维护成本太高了。黑犬立刻打电话把QD组叫了回来,决定用Mixin功能组合来实现这个功能:
/**
* Mixin 类(Mixins.hpp) - 通过多重继承组合功能
* 优势:不修改原类,遵循开闭原则,功能正交
*/
// ========== Mixin 1: 日志功能 ==========
template<typename Base>
class Loggable : public Base {
public:
using Base::Base; // 继承构造函数
void log(const std::string& message) const {
std::cout << " [日志] [" << Base::get_name() << "] " << message << "\n";
}
// 增强基类方法
void do_work() const {
log("开始工作");
Base::do_work();
log("工作完成");
}
};
// ========== Mixin 2: 指标采集 ==========
template<typename Base>
class MetricsCollector : public Base {
private:
mutable std::chrono::steady_clock::time_point start_time;
mutable size_t task_count = 0;
public:
using Base::Base;
void do_work() const {
start_time = std::chrono::steady_clock::now();
++task_count;
Base::do_work();
auto duration = std::chrono::steady_clock::now() - start_time;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
std::cout << " [指标] 任务 #" << task_count << " 耗时: " << ms << "ms\n";
}
size_t get_task_count() const { return task_count; }
};
// ========== Mixin 3: 权限控制 ==========
template<typename Base>
class Permissioned : public Base {
private:
int permission_level = 1;
public:
using Base::Base;
void set_permission_level(int level) { permission_level = level; }
int get_permission_level() const { return permission_level; }
void do_work() const {
if (permission_level < 2) {
std::cout << " [权限] " << Base::get_name()
<< " 权限不足 (需要等级 >= 2,当前: " << permission_level << ")\n";
return;
}
Base::do_work();
}
};
// 带日志的员工
template<typename EmployeeType>
using LoggableEmployee = Loggable<EmployeeType>;
// ...这样黑犬就不用修改每个员工类了,只要用Mixin组合出需要的功能即可,既满足了监管要求,又避免了代码重复和维护困难的问题,比如:
// 带日志的研究员
std::cout << "\n【组合 1: 带日志功能的员工】\n";
LoggableEmployee<QuantResearcher> alice_with_log(
EmployeeID{2001}, "Alice (日志版)", 600000, EmployeeLevel::PRINCIPAL
);
alice_with_log.do_work();创建员工时创建这样的对象,那么公司的QR在做策略研究时就会自动记录日志了,而不需要修改原有的 QuantResearcher 类。
🧐富婆的新需求
就在黑犬以为可以松口气时,周四午休时公司神秘大客户——富婆Esme打来电话:
“狗子啊,我这边朋友家孩子都想来你公司增长点经验,给我好好安排安排。我希望你们能给这些实习生设计一些灵活的奖金计算方案,听懂了么?”
好家伙,但我能说不吗?我不能。黑犬只好又找来了QD组,决定用Policy-Based Design策略模板设计来实现这个需求,把原来的员工类改成策略模板类,这样工作和奖金都能更加灵活地调整:
/**
* Policy-Based Design - 策略作为模板参数
* 优势:编译期绑定,完全内联,零开销
*/
// ========== 工作策略 Policy ==========
struct ResearchWorkPolicy {
static void execute_task(const std::string& name) {
std::cout << " [研究工作] " << name << " 正在分析数据并回测策略\n";
}
};
// ... 奖金策略也同步更改
// ========== Policy-Based Employee ==========
template<typename WorkPolicy, typename BonusPolicy>
class PolicyBasedEmployee {
private:
std::string name;
double base_salary;
double performance_score = 1.0;
public:
PolicyBasedEmployee(std::string n, double s)
: name(std::move(n)), base_salary(s) {}
void do_work() const {
std::cout << "\n【Policy-Based 员工: " << name << "】\n";
WorkPolicy::execute_task(name); // 编译期决定
}
double calculate_bonus() const {
return BonusPolicy::calculate(base_salary, performance_score);
}
void set_performance(double score) { performance_score = score; }
std::string get_name() const { return name; }
double get_salary() const { return base_salary; }
void show_bonus_info() const {
std::cout << " [奖金] " << name << " - " << BonusPolicy::name
<< ": $" << calculate_bonus() << "\n";
}
};
// ========== 类型别名 ==========
using PolicyResearcher = PolicyBasedEmployee<ResearchWorkPolicy, PerformanceBasedBonusPolicy>;哪里都是人情世故,谁叫富婆给钱多呢?黑犬只好硬着头皮新增了几个HC,还和HR打好了招呼,让实习生们都用上专门设计过的奖金计算方案。
🌋策略灾难
直到这周四晚上,Alice 研发了 200 多个不同的策略,有的用 C++,有的用 Python FFI,有的甚至是外部厂商提供的:
“老板,我们的策略现在五花八门:
黑犬头都大了,你就不能提前说吗?下次让研究组凑钱请QD组吃饭!黑犬只好又找来了QD组,决定用类型擦除(Type Erasure)技术来实现策略的统一管理:
/**
* Type Erasure - 类型擦除包装器
* 优势:无需继承,统一接口,值语义
* 类似 std::function 的实现原理
*/
class AnyStrategy {
private:
// 内部抽象接口
struct StrategyInterface {
virtual ~StrategyInterface() = default;
virtual void execute(const std::string& name) const = 0;
virtual std::string get_name() const = 0;
virtual std::unique_ptr<StrategyInterface> clone() const = 0;
};
// 模板实现类 - 包装任何类型
template<typename T>
struct StrategyImpl : StrategyInterface {
T strategy;
explicit StrategyImpl(T s) : strategy(std::move(s)) {}
void execute(const std::string& name) const override {
// 调用 T 的 execute 方法(无需继承)
strategy.execute(name);
}
std::string get_name() const override {
return std::string(strategy.name());
}
std::unique_ptr<StrategyInterface> clone() const override {
return std::make_unique<StrategyImpl>(strategy);
}
};
std::unique_ptr<StrategyInterface> impl;
public:
// 接受任何有 execute 和 name 方法的类型
template<typename T>
AnyStrategy(T strategy)
: impl(std::make_unique<StrategyImpl<T>>(std::move(strategy))) {}
// 拷贝构造
AnyStrategy(const AnyStrategy& other)
: impl(other.impl ? other.impl->clone() : nullptr) {}
// 移动构造
AnyStrategy(AnyStrategy&&) noexcept = default;
// 拷贝赋值
AnyStrategy& operator=(const AnyStrategy& other) {
if (this != &other) {
impl = other.impl ? other.impl->clone() : nullptr;
}
return *this;
}
// 移动赋值
AnyStrategy& operator=(AnyStrategy&&) noexcept = default;
// 统一接口
void execute(const std::string& name) const {
if (impl) {
impl->execute(name);
}
}
std::string get_name() const {
return impl ? impl->get_name() : "None";
}
};直接使用类型擦除重构了策略类之后,Alice现在终于能够统一的管理不同类型的策略了:
// 使用 AnyStrategy 管理不同类型的策略
std::vector<AnyStrategy> strategies;
strategies.emplace_back(MomentumStrategy{});
strategies.emplace_back(MeanReversionStrategy{});
strategies.emplace_back(MLStrategy{});
strategies.emplace_back(VendorStrategy{});
// 统一执行所有策略
std::cout << "\n【类型擦除管理的策略列表】\n";
for (const auto& strat : strategies) {
strat.execute("Alice");
}🌈雨后彩虹
经过一周的奋战,黑犬终于带领QD组解决了公司面临的各种棘手问题,现在比AlphaZ的订单执行速度 快了 30 微秒!气得白猫直跳脚。富婆Esme也非常满意,追加了 2000 万投资。黑犬站在办公室落地窗前,看着团队成员在工位上专注工作。
财务助理递来一杯马黛茶🧉:
“老板,前辈 A 发来邮件,说想介绍几个 LP(有限合伙人)过来。大姐头Esme说周天晚上八点也撺了一个和投资人朋友的局,要你一定抽时间去。”
黑犬笑了笑说:
“告诉他,SigmaX 已经准备好了。技术债还清了,架构优化了,团队专业化了。是时候进入下一个阶段了。”
📒补充说明
本周涉及了多种C++高级编程技术,你可能好奇模板元编程怎么没有单独讲,其实这些技术本质上都离不开模板元编程的支持,比如CRTP、Mixin、Policy-Based Design和类型擦除都大量使用了模板技术来实现编译期多态和类型抽象,模板元编程的几个重要特征(如SFINAE、constexpr if、概念等)在这些技术中都有所体现,比如:
-
CRTP 利用模板参数实现静态多态,避免了虚函数开销。
-
Mixin 通过模板继承实现功能组合,遵循开闭原则。
-
Policy-Based Design 通过模板参数实现策略注入,增强了灵活性。
-
类型擦除 通过模板实现对任意类型的封装,实现统一接口。
教科书对模板元编程的介绍通常比较抽象,而通过这些具体技术的应用,可以更直观理解这几项技术适用于什么场景,以及它们如何利用模板元编程的特性来实现高效、灵活的设计。
本周代码已经上传到Github仓库🔗:https://github.com/shuheng-mo/qd-study-plan-104wk.git,欢迎Star⭐、贡献代码或issue。
下周预告:W06 - C++的OOP高级用法(下)
👋各位下周五见,下周SigmaX将会迎来更加硬核的挑战。