必须用不兼容的模板类型(如meter_t和kilometer_t)替代命名或注释,因其在编译期强制量纲检查:同量纲可运算,异量纲拒绝,缩放系数与constexpr保障编译期安全,且operator double()须explicit防隐式绕过。

为什么不能只靠命名或注释来防单位错误
命名像 distance_in_meters 或注释 // in km 在编译期完全不检查——传错值、算错倍数、漏写换算,编译器照过。真正防错需要让 meter_t 和 kilometer_t 成为**不兼容的类型**,哪怕底层都是 double。
用模板参数区分量纲和量级:核心是 unit_t 模板
定义一个带维度(length/mass/time)和缩放系数(1.0 / 1000.0)的模板:
template<int DimLength, int DimMass, int DimTime, double Scale = 1.0>
struct unit_t {
double value;
constexpr explicit unit_t(double v) : value(v * Scale) {}
constexpr operator double() const { return value / Scale; }
};
这样就能写出互不转换的类型:
using meter_t = unit_t;using kilometer_t = unit_t;-
using second_t = unit_t;(和长度类型天然不兼容)
直接写 kilometer_t{5.0} + meter_t{100.0} 会编译失败——没定义跨类型加法,必须显式转:
立即学习“C++免费学习笔记(深入)”;
meter_t m = kilometer_t{5.0}; // 隐式缩放:5.0 km → 5000.0 m
加减乘除运算符必须按量纲规则重载
不能无脑支持所有 operator+,否则 meter_t + second_t 也能过。只允许同量纲相加:
template<int L, int M, int T, double S1, double S2>
constexpr auto operator+(unit_t<L,M,T,S1> a, unit_t<L,M,T,S2> b) {
return unit_t<L,M,T,1.0>{a.value + b.value};
}
乘法则要合成量纲:
-
meter_t × second_t → unit_t(米·秒) -
meter_t / second_t → unit_t(米/秒) - 乘两个
meter_t得square_meter_t,不是meter_t
不写这些特化,编译器就拒绝运算——这才是类型安全的源头。
实际使用时最容易忽略的三个坑
一是 constexpr 不够:如果构造函数或运算符没标 constexpr,就无法在编译期做单位推导(比如模板参数推导或 static_assert 检查);
二是缩放系数用 double 常量而非模板非类型参数:像 unit_t 中的 1000.0 必须是字面量常量,否则无法作为模板实参;
三是忘记禁用隐式转换:如果允许 unit_t 自动转 double,那 void f(double); f(meter_t{1.0}); 就绕过检查了——必须把 operator double() 设为 explicit,并只在明确需要裸数值时用 static_cast<double>(x)</double>。


















