std::midpoint仅防有符号整型加法中间溢出,不防结果截断、无符号/浮点/指针溢出、业务逻辑错误及内存安全问题。

直接用 std::midpoint 替换 (a + b) / 2 并不能自动规避溢出——它只在整型类型严格匹配、参数同为有符号且编译启用 C++20 时,才真正防加法中间溢出;对无符号、浮点、指针跨数组等场景,要么语义错位,要么编译失败,要么行为未定义。
std::midpoint(int, int) 能防什么溢出、不能防什么
它防的是有符号整数加法中间步骤的溢出(如 INT_MAX + (INT_MAX - 1)),不是“结果太大”的问题。底层等价于 a + (b - a) / 2 或位运算组合,所有中间值都在类型表示范围内。
-
std::midpoint(INT_MAX, INT_MAX - 1)返回确定值INT_MAX - 1,而(INT_MAX + INT_MAX - 1) / 2是未定义行为(UB) - 它不防结果截断:若你用
short调用,返回仍是short,不会自动提升精度 - 它不校验
a和b是否构成有效区间——std::midpoint(100, 1)合法,返回50,但业务上可能本应要求a - 对负数,它向下取整:
std::midpoint(-3, 2)返回-1,和(-3 + 2) / 2一致,不是四舍五入或向零取整
为什么 std::midpoint(size_t, size_t) 看似安全却容易误用
无符号类型减法会回绕(wrap around),std::midpoint 接受这点并按模算术定义中点。但多数离散坐标场景(如数组索引、网格行列)并不需要模语义。
-
std::midpoint(1U, 0U)返回0U(因(1U - 0U) / 2 == 0),数学上不符合直觉中的“中点” - 若你传入
left = 10U、right = 5U(即区间反向),结果是10U + (5U - 10U) / 2→10U + UINT_MAX/2 + 1,回绕后完全不可预测 - 业务上应先保证
left ,再调用;否则不如显式写 <code>left + (right - left) / 2并加断言 - 若真需模意义中点(如环形缓冲区哈希桶),要明确文档化该假设,而非默认
std::midpoint就“更安全”
std::midpoint 指针版本是唯一不可替代的用途
这是它最刚性、最不可替代的价值点:计算大数组边界指针的中点时,标准强制要求实现避免 end - begin 超出 ptrdiff_t 范围导致的未定义行为。
- 传统写法
begin + (end - begin) / 2中,若end - begin > PTRDIFF_MAX,减法本身已是 UB;std::midpoint(begin, end)明确绕过该峰值 - 必须满足:两个指针同为
T*类型(const修饰也需一致)、指向同一数组(含 one-past-the-end)、不能是void*或不同分配块的地址 -
std::midpoint(&v[0], &v[0] + v.size())合法;std::midpoint(v.begin(), v.end())编译失败(迭代器非原生指针) - 空容器必须提前判断:
v.empty()为真时,&v[0]本身非法,std::midpoint不做任何检查
浮点坐标别用 std::midpoint(float, float)
它只是 (a + b) * 0.5 的封装,不规避溢出,不改善精度,某些情况下比手写更差。
-
std::midpoint(1e30f, 1.0f)极大概率返回1e30f(加法丢失低位),而a + (b - a) * 0.5f更接近真实中点 - C++20 标准未要求浮点重载具备抗溢出能力;部分编译器虽允许,但属“语法合法,语义无增益”
- 若需稳健浮点中点,优先分支判断量级:
if (std::abs(a) >= std::abs(b)) { return a + (b - a) * 0.5f; },或用std::fma(a, 0.5f, b * 0.5f) -
std::midpoint对NaN、inf无特殊处理,和裸算一样传播异常值
最容易被忽略的是:它只保“算得对”,不保“用得对”。传进去两个悬空指针,它可能“成功”返回一个地址;传进去越界索引,它照样算出数字——数值安全不等于内存安全,也不等于业务逻辑正确。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











