游戏之作: QVariant的解析和更新
纯属好玩的实现.
我有一个很大的数据结构, 有很多的成员变量, 几十个. 中间涉及到更新, 更新的数据源是外部传过来的一个json对象, 被解析为一个QVariantMap
或QVariantHash
. 对于每个属性, 我们需要判断传进来的Map中有没有包含, 如果包含了, 还要判断是否有意义(例如, 对QString
, 是否为空). 只有都满足了, 才会使用它来更新结构中的属性. 属性类型包括整数, 浮点数, 日期类型, 字符串和QByteArray
码流. 这里一个很麻烦的问题是, 比如, QString
, QDate
, 判断值有效性是不同的函数. QString
和QByteArray
是isEmpty()
, 而QDate
和QDateTime
则是isValid()
.
如果每个都使用if...else
判断, 实在太麻烦, 还要记住每个成员的类型. 这个问题只有C++能够给出令人满意的解决方案.
实现0:
可以用宏实现. 这里主要的问题是需要自己处理类型. 仍然需要记住每个变量的转换函数. 在C++11之前, 大概只能这么实现.
实现1:
利用C++17的静态if语句功能, 直接定义一个模板函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #define SET_VALID_VALUE(a, b, c) do{ if(c){a=b; }}while(0);
template<typename T, typename M> void assignFromMap(T& a, const M& b, const QString& c) { if constexpr (std::_Is_any_of_v<M, QVariantMap, QVariantHash>) { if(!b.contains(c)) { return; } if constexpr (std::is_same_v<T, bool>) { a = b.value(c).toBool(); } else if constexpr (std::is_integral_v<T>) { a = b.value(c).toInt(); } else if constexpr (std::is_floating_point_v<T>) { a = b.value(c).toDouble(); } else if constexpr (std::is_same_v<T, QString>) { SET_VALID_VALUE(a, b.value(c).toString(), !b.value(c).toString().isEmpty()); } else if constexpr (std::is_same_v<T, QByteArray>) { SET_VALID_VALUE(a, b.value(c).toByteArray(), !b.value(c).toByteArray().isEmpty()); } else if constexpr (std::is_same_v<T, QDateTime>) { SET_VALID_VALUE(a, b.value(c).toDateTime(), !b.value(c).toDateTime().isValie()); } else if constexpr (std::is_same_v<T, QDate>) { SET_VALID_VALUE(a, b.value(c).toDate(), !b.value(c).toDate().isValid()); } else{ undeclared(b,c); } } else { undeclared(b,c); } }
|
上面的代码中的undeclared()
函数是故意留下的一个未实现的函数, 当assignFromMap()
函数遇到参数是未定义的类型时, 就会在这里编译出错, 很容易找到问题所在.
如果是C++20, 我们可以利用concept
来提高编译器错误信息的可读性, 但是对于这种情况, 远不如这种方式来的更直观. 并且, 我们的产品也只是使用C++17标准, 未迁移到C++20.
就可以这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| auto iter = std::find_if(_impl->_cases.begin(), _impl->_cases.end(), [case_id](const auto& a){ return a._basic._id==case_id; }); RETURN_LOG_IF(iter==_impl->_cases.end(), QString("错误: 找不到case_id=%1的记录!").arg(case_id)); assignFromMap(iter->_basic._case_no, info, "caseNo"); assignFromMap(iter->_basic._summary, info, "diagnosis"); assignFromMap(iter->_basic._age, info, "age"); assignFromMap(iter->_basic._age_unit, info, "ageUnit"); assignFromMap(iter->_basic._identity, info, "identity"); assignFromMap(iter->_basic._sex, info, "gender"); assignFromMap(iter->_basic._sufferer, info, "name"); assignFromMap(iter->_basic._admission_no,info, "admission_no"); assignFromMap(iter->_basic._bed_no, info, "BedNo"); assignFromMap(iter->_basic._department, info, "department"); ...
|
代码逻辑要清晰明了多了.
实现2:
按照C++规范, 静态if的处理仍然是从上到下进行的, 并不是遵循最优匹配原则. 而参考C++17的visitor
的实现的overload
则是完全按照模板匹配实现的. 下面的实现做了简化, 没有检查参数有效性. 但是基本思想是一样的:
1 2 3 4 5 6 7
| template <typename... Ts> struct overload : Ts... { using Ts::operator()...; }; template<typename ... Ts> overload(Ts ...) -> overload<Ts ...>;
|
定义这个复制函数对象:
1 2 3 4 5 6 7
| auto assign = overload{ [](bool& a, const QVariantMap& b, const QString& c){ a=b.value(c).toBool(); }, [](int& a, const QVariantMap& b, const QString& c){ a= b.value(c).toInt();}, [](double& a, const QVariantMap& b, const QString& c) { a=b.value(c).toDouble(); }, [](QString& a, const QVariantMap& b, const QString& c){a=b.value(c).toString(); }, [](QDate& a, const QVariantMap& b, const QString& c){a=b.value(c).toDate(); }, };
|
QVariant和std::variant
两者有着很大的差异, 虽然都叫variant. 从某种意义上, 或许std::any
有点像QVariant
.