1. C++过滤器模式深度解析
过滤器模式(Filter Pattern)是C++中一种结构型设计模式,它允许我们按照特定条件筛选对象集合。这种模式在数据处理、日志系统、游戏开发等领域应用广泛,特别是在需要多层筛选逻辑的场景中优势明显。
我在实际项目中最常用到过滤器模式的场景是游戏开发中的碰撞检测系统。比如需要从数百个游戏对象中筛选出满足以下条件的对象:位于玩家半径5米内、生命值大于0、属于敌方阵营。传统if-else嵌套写法会让代码难以维护,而过滤器模式通过链式调用可以优雅地解决这个问题。
2. 过滤器模式的核心结构
2.1 基础类设计
标准的过滤器模式包含三个核心组件:
// 过滤标准接口 class Criteria { public: virtual bool meetCriteria(const GameObject& obj) const = 0; virtual ~Criteria() = default; }; // 具体过滤实现 class HealthCriteria : public Criteria { float minHealth; public: explicit HealthCriteria(float health) : minHealth(health) {} bool meetCriteria(const GameObject& obj) const override { return obj.health >= minHealth; } }; // 过滤容器 class Filter { public: static std::vector<GameObject> filterObjects( const std::vector<GameObject>& objects, const Criteria& criteria) { std::vector<GameObject> result; for (const auto& obj : objects) { if (criteria.meetCriteria(obj)) { result.push_back(obj); } } return result; } };2.2 组合过滤器
过滤器模式的强大之处在于可以组合多个条件:
class AndCriteria : public Criteria { const Criteria& first; const Criteria& second; public: AndCriteria(const Criteria& f, const Criteria& s) : first(f), second(s) {} bool meetCriteria(const GameObject& obj) const override { return first.meetCriteria(obj) && second.meetCriteria(obj); } }; // 使用示例 HealthCriteria healthy(50.0f); FactionCriteria enemy(Faction::Enemy); AndCriteria healthyEnemy(healthy, enemy); auto targets = Filter::filterObjects(gameObjects, healthyEnemy);3. 现代C++的优化实现
3.1 使用lambda表达式
C++11之后,我们可以用lambda简化过滤器实现:
auto filter = [](const auto& objects, auto predicate) { std::vector<std::decay_t<decltype(objects[0])>> result; std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), predicate); return result; }; // 使用示例 auto results = filter(gameObjects, [](const GameObject& obj) { return obj.health > 50 && obj.faction == Faction::Enemy; });3.2 模板元编程实现
对于性能敏感场景,可以使用编译期过滤器:
template <typename T, typename Predicate> constexpr auto compileTimeFilter(const T& container, Predicate p) { T result{}; for (const auto& item : container) { if (p(item)) { result.insert(result.end(), item); } } return result; } // 使用示例 constexpr std::array<int, 5> arr{1,2,3,4,5}; constexpr auto evens = compileTimeFilter(arr, [](int x) { return x % 2 == 0; }); static_assert(evens.size() == 2);4. 性能优化技巧
4.1 缓存友好设计
过滤器模式可能造成内存碎片,我们可以优化内存访问:
class BatchFilter { std::vector<size_t> indices; // 预分配内存 public: template <typename Container, typename Predicate> const Container& filter(Container& c, Predicate p) { indices.clear(); for (size_t i = 0; i < c.size(); ++i) { if (p(c[i])) indices.push_back(i); } // 原地交换元素避免拷贝 for (size_t i = 0; i < indices.size(); ++i) { std::swap(c[i], c[indices[i]]); } c.resize(indices.size()); return c; } };4.2 并行过滤
对于大型数据集,可以使用并行算法:
#include <execution> auto parallelFilter = [](const auto& container, auto pred) { std::vector<std::decay_t<decltype(container[0])>> result; std::mutex mutex; std::for_each(std::execution::par, container.begin(), container.end(), [&](const auto& item) { if (pred(item)) { std::lock_guard lock(mutex); result.push_back(item); } }); return result; };5. 实际应用案例
5.1 游戏中的AI决策系统
在游戏AI中,我们经常需要筛选符合条件的攻击目标:
class TargetSelector { std::vector<std::unique_ptr<Criteria>> criteriaChain; public: TargetSelector& addCriteria(std::unique_ptr<Criteria> crit) { criteriaChain.push_back(std::move(crit)); return *this; } std::vector<GameObject> selectTargets( const std::vector<GameObject>& candidates) const { std::vector<GameObject> results = candidates; for (const auto& criteria : criteriaChain) { results = Filter::filterObjects(results, *criteria); if (results.empty()) break; // 提前终止 } return results; } }; // 构建过滤链 TargetSelector selector; selector.addCriteria(std::make_unique<RangeCriteria>(player, 5.0f)) .addCriteria(std::make_unique<HealthCriteria>(0.0f)) .addCriteria(std::make_unique<FactionCriteria>(Faction::Enemy));5.2 日志系统过滤
在日志系统中实现多级过滤:
class LogFilter : public Criteria { LogLevel minLevel; std::string keyword; public: LogFilter(LogLevel level, std::string_view kw) : minLevel(level), keyword(kw) {} bool meetCriteria(const LogEntry& entry) const override { return entry.level >= minLevel && entry.message.find(keyword) != std::string::npos; } }; // 使用示例 LogFilter errorFilter(LogLevel::Error, "timeout"); auto criticalLogs = Filter::filter(logEntries, errorFilter);6. 常见问题与解决方案
6.1 过滤器顺序优化
过滤器的应用顺序会影响性能。通常应该:
- 将最严格的条件放在前面
- 将计算量小的条件放在前面
- 考虑各条件的过滤比例
// 不好的顺序 - 先进行昂贵计算 selector.addCriteria(std::make_unique<DistanceCalcCriteria>()) // 昂贵 .addCriteria(std::make_unique<TypeCriteria>()); // 简单 // 优化后的顺序 selector.addCriteria(std::make_unique<TypeCriteria>()) // 先过滤掉大部分 .addCriteria(std::make_unique<DistanceCalcCriteria>()); // 对少量对象计算6.2 动态过滤器注册
实现运行时可配置的过滤器系统:
class FilterFactory { std::unordered_map<std::string, std::function<std::unique_ptr<Criteria>(const Json&)>> creators; public: template <typename T> void registerFilter(const std::string& name) { creators[name] = [](const Json& config) { return std::make_unique<T>(config); }; } std::unique_ptr<Criteria> create( const std::string& name, const Json& config) const { if (auto it = creators.find(name); it != creators.end()) { return it->second(config); } throw std::runtime_error("Unknown filter type"); } }; // 注册过滤器类型 factory.registerFilter<HealthCriteria>("health"); factory.registerFilter<RangeCriteria>("range"); // 从配置文件创建过滤链 auto criteria = factory.create(json["type"], json["config"]);7. 测试策略
7.1 单元测试过滤器
为过滤器编写全面的测试用例:
TEST(HealthFilterTest, FiltersCorrectly) { std::vector<GameObject> objects { GameObject{100}, GameObject{50}, GameObject{10} }; HealthCriteria criteria(50.0f); auto result = Filter::filterObjects(objects, criteria); ASSERT_EQ(result.size(), 2); EXPECT_GE(result[0].health, 50.0f); EXPECT_GE(result[1].health, 50.0f); } TEST(CompositeFilterTest, AndConditionWorks) { HealthCriteria health(30.0f); FactionCriteria faction(Faction::Enemy); AndCriteria combined(health, faction); GameObject healthyEnemy{40, Faction::Enemy}; GameObject healthyAlly{40, Faction::Ally}; EXPECT_TRUE(combined.meetCriteria(healthyEnemy)); EXPECT_FALSE(combined.meetCriteria(healthyAlly)); }7.2 性能测试
比较不同实现方式的性能:
void benchmarkFilters() { std::vector<GameObject> objects(1000000); // 填充测试数据... BENCHMARK("Traditional filter") { return Filter::filterObjects(objects, HealthCriteria(50.0f)); }; BENCHMARK("Lambda filter") { return filter(objects, [](const auto& obj) { return obj.health >= 50.0f; }); }; BENCHMARK("Parallel filter") { return parallelFilter(objects, [](const auto& obj) { return obj.health >= 50.0f; }); }; }8. 扩展模式变体
8.1 装饰器模式结合
通过装饰器模式动态添加过滤条件:
class DecoratedCriteria : public Criteria { protected: const Criteria& wrapped; public: explicit DecoratedCriteria(const Criteria& crit) : wrapped(crit) {} }; class NotCriteria : public DecoratedCriteria { public: using DecoratedCriteria::DecoratedCriteria; bool meetCriteria(const GameObject& obj) const override { return !wrapped.meetCriteria(obj); } }; // 使用示例 HealthCriteria healthy(50.0f); NotCriteria notHealthy(healthy); // 过滤不健康的对象8.2 策略模式切换
在不同过滤算法间动态切换:
class FilterStrategy { std::unique_ptr<Criteria> strategy; public: void setStrategy(std::unique_ptr<Criteria> newStrategy) { strategy = std::move(newStrategy); } std::vector<GameObject> apply( const std::vector<GameObject>& objects) const { if (!strategy) return {}; return Filter::filterObjects(objects, *strategy); } }; // 运行时切换策略 FilterStrategy strategy; strategy.setStrategy(std::make_unique<HealthCriteria>(30.0f)); auto result1 = strategy.apply(objects); strategy.setStrategy(std::make_unique<RangeCriteria>(player, 10.0f)); auto result2 = strategy.apply(objects);9. 与其他模式的协作
9.1 与观察者模式结合
实现动态更新的过滤器:
class ObservableCriteria : public Criteria, public Observable { std::unique_ptr<Criteria> baseCriteria; public: explicit ObservableCriteria(std::unique_ptr<Criteria> crit) : baseCriteria(std::move(crit)) {} void updateCriteria(std::unique_ptr<Criteria> newCrit) { baseCriteria = std::move(newCrit); notifyObservers(); } bool meetCriteria(const GameObject& obj) const override { return baseCriteria->meetCriteria(obj); } }; // 使用示例 auto observable = std::make_shared<ObservableCriteria>( std::make_unique<HealthCriteria>(50.0f)); // 当需要修改条件时 observable->updateCriteria(std::make_unique<HealthCriteria>(70.0f));9.2 与工厂模式结合
创建可配置的过滤器工厂:
class CriteriaFactory { public: static std::unique_ptr<Criteria> createHealthFilter(float minHealth) { return std::make_unique<HealthCriteria>(minHealth); } static std::unique_ptr<Criteria> createRangeFilter( const GameObject& center, float radius) { return std::make_unique<RangeCriteria>(center, radius); } static std::unique_ptr<Criteria> createAndFilter( std::unique_ptr<Criteria> lhs, std::unique_ptr<Criteria> rhs) { return std::make_unique<AndCriteria>(*lhs, *rhs); } }; // 使用工厂创建复杂过滤器 auto filter = CriteriaFactory::createAndFilter( CriteriaFactory::createHealthFilter(50.0f), CriteriaFactory::createRangeFilter(player, 10.0f) );10. 最佳实践总结
经过多个项目的实践验证,我总结了以下C++过滤器模式的最佳实践:
优先使用标准库算法:在简单场景下,
std::copy_if+lambda通常比完整实现过滤器模式更简洁注意过滤器对象的生命周期:组合过滤器时,确保被组合的过滤器生命周期足够长
考虑异常安全性:过滤器函数应该是nothrow的,避免在过滤过程中抛出异常
性能关键路径避免虚函数:在性能敏感场景,可以考虑CRTP模式替代虚函数
提供良好的诊断信息:为过滤器实现
operator<<,便于调试时输出当前过滤条件
std::ostream& operator<<(std::ostream& os, const Criteria& crit) { if (auto hc = dynamic_cast<const HealthCriteria*>(&crit)) { return os << "Health >= " << hc->minHealth; } // 其他类型的输出... return os << "Unknown criteria"; }支持撤销操作:考虑实现逆向过滤器,可以恢复被过滤掉的元素
内存分配优化:预分配结果容器内存,避免多次重新分配
线程安全考虑:如果过滤器需要修改内部状态,确保适当的同步机制
支持序列化:为过滤器实现序列化接口,便于保存和加载过滤条件
文档化过滤语义:明确记录每个过滤器的前置条件和后置条件