C++类型安全测量单位库实现:解决代码重复与跨类型运算问题
C++类型安全物理单位库实现方案
核心思路
用CRTP(奇异递归模板模式)封装通用逻辑,配合编译期Concept检查,既避免重复编写构造、运算符等代码,又严格限制跨物理类别的非法运算。核心是用「类别标签(Tag)」区分不同物理量(长度/质量),只有同标签的类型才能参与运算。
1. 定义物理类别标签
先给每个物理量类别定义空标签类,作为类型区分的标识:
struct LengthTag {}; struct MassTag {};
2. 实现CRTP基类
把通用的构造、运算符、单位转换逻辑放在基类中,通过模板参数绑定子类类型和物理标签,同时用Concept限制运算的合法性:
#include <ratio> #include <concepts> // 定义Concept:判断两个单位是否属于同一物理类别 template<typename T, typename U> concept SamePhysicalCategory = std::is_same_v<typename T::category_tag, typename U::category_tag>; // CRTP基类,封装通用逻辑 template<typename Derived, typename CategoryTag, typename Rep, typename Ratio = std::ratio<1>> class Measurement { public: using category_tag = CategoryTag; using rep = Rep; using ratio = Ratio; // 构造函数 constexpr explicit Measurement(Rep value) : m_value(value) {} // 获取原始数值 constexpr Rep value() const noexcept { return m_value; } // 同类别单位加法 template<SamePhysicalCategory<Derived> Other> constexpr Derived operator+(const Other& other) const noexcept { // 转换为当前单位的数值后相加 const auto converted = other.value() * static_cast<Rep>(Other::ratio::num) * static_cast<Rep>(ratio::den) / static_cast<Rep>(Other::ratio::den) / static_cast<Rep>(ratio::num); return Derived(m_value + converted); } // 同类别单位减法 template<SamePhysicalCategory<Derived> Other> constexpr Derived operator-(const Other& other) const noexcept { const auto converted = other.value() * static_cast<Rep>(Other::ratio::num) * static_cast<Rep>(ratio::den) / static_cast<Rep>(Other::ratio::den) / static_cast<Rep>(ratio::num); return Derived(m_value - converted); } // 标量乘法 constexpr Derived operator*(Rep scalar) const noexcept { return Derived(m_value * scalar); } // 标量除法 constexpr Derived operator/(Rep scalar) const noexcept { return Derived(m_value / scalar); } // 单位转换:转换为目标单位类型 template<typename TargetDerived> requires SamePhysicalCategory<Derived, TargetDerived> constexpr TargetDerived convert() const noexcept { using TargetRatio = typename TargetDerived::ratio; const auto factor = static_cast<Rep>(Ratio::num) * static_cast<Rep>(TargetRatio::den) / static_cast<Rep>(Ratio::den) / static_cast<Rep>(TargetRatio::num); return TargetDerived(m_value * factor); } private: Rep m_value; };
3. 定义具体单位类型
基于CRTP基类快速定义各种单位,只需指定类别标签、数值类型和比例:
// 长度单位:米(基准单位,比例1:1) template<typename Rep = double> class Meter : public Measurement<Meter<Rep>, LengthTag, Rep> { using Base = Measurement<Meter<Rep>, LengthTag, Rep>; public: using Base::Base; }; // 长度单位:英尺(1英尺=0.3048米) template<typename Rep = double> class Feet : public Measurement<Feet<Rep>, LengthTag, Rep, std::ratio<3048, 10000>> { using Base = Measurement<Feet<Rep>, LengthTag, Rep, std::ratio<3048, 10000>>; public: using Base::Base; }; // 质量单位:千克(基准单位) template<typename Rep = double> class Kilogram : public Measurement<Kilogram<Rep>, MassTag, Rep> { using Base = Measurement<Kilogram<Rep>, MassTag, Rep>; public: using Base::Base; }; // 质量单位:克(1克=0.001千克) template<typename Rep = double> class Gram : public Measurement<Gram<Rep>, MassTag, Rep, std::ratio<1, 1000>> { using Base = Measurement<Gram<Rep>, MassTag, Rep, std::ratio<1, 1000>>; public: using Base::Base; };
4. 测试与验证
合法操作(编译通过)
int main() { // 同类别单位运算 Meter<double> m1(5.0); Feet<double> f1(10.0); auto total_m = m1 + f1.convert<Meter<double>>(); // 英尺转米后相加 auto total_f = f1 + m1.convert<Feet<double>>(); // 米转英尺后相加 // 标量运算 auto m2 = m1 * 2.0; auto f2 = f1 / 2.0; // 单位转换 Gram<double> g(1500.0); auto kg = g.convert<Kilogram<double>>(); // 1500克转1.5千克 }
非法操作(编译报错)
// 跨类别运算:长度+质量,触发Concept检查失败 Meter<double> m(1.0); Kilogram<double> kg(1.0); auto invalid = m + kg; // 编译错误:SamePhysicalCategory不满足
关键优势
- 零重复代码:所有通用逻辑都在CRTP基类中,新增单位只需继承并指定参数
- 严格类型安全:跨物理类别的运算会在编译期直接报错,完全避免运行时错误
- 灵活单位转换:基于
std::ratio的编译期计算,保证转换精度和效率
内容的提问来源于stack exchange,提问作者FalcoGer
相关产品推荐
相关产品推荐

