如何在C++中实现类似ODB的简洁链式查询功能?
实现ODB风格的C++简洁过滤语法
ODB的简洁查询语法核心是通过重载操作符构建可执行的表达式树,每个表达式节点封装具体的判断逻辑,最终直接用自然的语法完成对象过滤。以下是基于这个思路的完整实现方案,替代你当前繁琐的Specification模式:
核心思路
抛弃虚函数基类的Specification,改用值语义的表达式对象,通过模板和操作符重载,将System::id == 1 && System::model.name == "MyModelName"这类语法转换为可执行的判断逻辑,避免手动创建各类Specification子类的冗余代码。
分步实现
1. 字段代理类:关联对象字段与访问逻辑
先定义一个Field模板类,用来封装对象字段的访问方式(支持直接成员和嵌套成员):
#include <functional> #include <QList> #include <QString> template <typename T, typename FieldType> struct Field { // 支持任意可调用对象作为字段访问器(直接成员、嵌套成员都能处理) using Getter = std::function<const FieldType&(const T&)>; Getter getter; explicit Field(Getter g) : getter(std::move(g)) {} // 重载==操作符,返回相等比较表达式 auto operator==(const FieldType& val) const { return EqualExpr<T, FieldType>{*this, val}; } // 可扩展重载>、<、!=、>=、<=等操作符 auto operator>(const FieldType& val) const { return GreaterExpr<T, FieldType>{*this, val}; } };
2. 表达式节点:封装比较与逻辑运算
每个表达式节点实现eval方法,接收对象实例并返回判断结果,用CRTP避免虚函数开销:
// 基础表达式接口(CRTP) template <typename Derived, typename T> struct Expr { bool operator()(const T& obj) const { return static_cast<const Derived*>(this)->eval(obj); } }; // 相等比较表达式 template <typename T, typename FieldType> struct EqualExpr : Expr<EqualExpr<T, FieldType>, T> { const Field<T, FieldType>& field; FieldType value; EqualExpr(const Field<T, FieldType>& f, FieldType v) : field(f), value(std::move(v)) {} bool eval(const T& obj) const { return field.getter(obj) == value; } }; // 大于比较表达式 template <typename T, typename FieldType> struct GreaterExpr : Expr<GreaterExpr<T, FieldType>, T> { const Field<T, FieldType>& field; FieldType value; GreaterExpr(const Field<T, FieldType>& f, FieldType v) : field(f), value(std::move(v)) {} bool eval(const T& obj) const { return field.getter(obj) > value; } }; // 逻辑与表达式 template <typename T, typename LhsExpr, typename RhsExpr> struct AndExpr : Expr<AndExpr<T, LhsExpr, RhsExpr>, T> { LhsExpr lhs; RhsExpr rhs; AndExpr(LhsExpr l, RhsExpr r) : lhs(std::move(l)), rhs(std::move(r)) {} bool eval(const T& obj) const { return lhs(obj) && rhs(obj); } }; // 全局重载&&操作符,用于拼接两个表达式 template <typename T, typename Lhs, typename Rhs> auto operator&&(const Expr<Lhs, T>& lhs, const Expr<Rhs, T>& rhs) { return AndExpr<T, Lhs, Rhs>{static_cast<const Lhs&>(lhs), static_cast<const Rhs&>(rhs)}; }
3. 为目标类定义字段代理
修改Model和System类,添加getter方法并定义静态字段代理(支持嵌套字段):
class Model { int id; QString name; public: int getId() const { return id; } const QString& getName() const { return name; } // 静态字段代理 static inline const Field<Model, int> id{&Model::getId}; static inline const Field<Model, QString> name{&Model::getName}; }; class System { int id; int user_id; Model model; public: int getId() const { return id; } int getUserId() const { return user_id; } const Model& getModel() const { return model; } // 静态字段代理 static inline const Field<System, int> id{&System::getId}; static inline const Field<System, int> user_id{&System::getUserId}; // 嵌套字段:用lambda封装访问逻辑 static inline const Field<System, QString> model_name{ [](const System& s) -> const QString& { return s.getModel().getName(); } }; };
4. Query类与Filter适配
Query类直接封装表达式对象,Filter接收Query完成过滤:
template <typename T, typename ExprType> struct Query { ExprType expr; explicit Query(ExprType e) : expr(std::move(e)) {} bool matches(const T& obj) const { return expr(obj); } }; template <typename T> struct Filter { QList<T*> filter(QList<T*> items, const auto& query) { QList<T*> result; for (auto item : items) { if (query.matches(*item)) { result.push_back(item); } } return result; } };
最终使用方式
现在可以写出你期望的简洁代码:
// 假设已初始化的System实例列表 QList<System*> system_list; Filter<System> system_filter; // 直接用自然语法构建查询 auto system_query = Query<System>(System::id == 1 && System::model_name == "MyModelName"); auto filtered_list = system_filter.filter(system_list, system_query);
扩展方向
- 支持逻辑或
||、逻辑非!操作符,实现方式参考AndExpr - 添加正则匹配表达式,重载
operator~实现模糊查询 - 支持C++17类模板参数推导,简化
Query声明为auto query = Query{...}
内容的提问来源于stack exchange,提问作者Mario de la Rosa
相关产品推荐
相关产品推荐

