应在interval构造函数中强制校验起点和终点,start>end时抛std::invalid_argument异常或交换归正,并用const成员+初始化列表防止篡改,外部输入需先用std::min/std::max预处理,assert仅用于调试不可替代运行时检查。

用构造函数强制校验起点和终点
最直接的方式是在 Interval 类的构造函数里做检查,发现 start > end 就抛异常或自动修正。不依赖用户自觉传入合法值,而是把校验逻辑收口到对象创建这一关。
常见错误是只在 setter 里检查,但忘了构造时也可能传错;或者干脆没检查,后续计算(比如长度、交集)直接出负数或逻辑崩溃。
- 推荐抛
std::invalid_argument:语义明确,调用方能感知非法输入 - 如果业务允许“自动归正”,可交换赋值:
if (start > end) std::swap(start, end);,但需文档注明该行为 - 避免在构造函数里默默静默修复却不留痕迹——调试时会找不到问题源头
用 const 成员 + 初始化列表杜绝中途篡改
起点和终点一旦设好就不该被修改,否则区间语义就松动了。把 start 和 end 声明为 const,并仅通过初始化列表赋值,能从语言层面阻止后续意外写入。
有人用普通成员变量 + public setter,结果在多线程或复杂调用链中被反复修改,导致某次 start > end 却没触发检查——const 是更彻底的防御。
-
const成员必须在初始化列表中赋值,不能在构造函数体内赋值 - 如果真需要修改区间,应返回新对象(如
with_start(int s)),而非修改原对象 - 注意:
const不影响移动语义,也不妨碍使用std::move
配合 std::clamp 或自定义比较逻辑处理边界输入
当起点终点来自外部不可信源(比如用户输入、文件解析、网络数据),光靠构造函数检查不够,还得预处理。C++17 的 std::clamp 很适合做兜底归一化。
例如读配置得到两个整数,但无法保证大小关系,此时先 clamp 再构造更安全:
int raw_lo = /* ... */;
int raw_hi = /* ... */;
int lo = std::min(raw_lo, raw_hi);
int hi = std::max(raw_lo, raw_hi);
Interval i{lo, hi}; // 构造函数仍做二次检查,形成双重防护
-
std::clamp本身不解决区间语义,它只是辅助工具;核心校验仍在类内部 - 对浮点类型要注意
NaN:std::min/std::max遇到NaN会返回NaN,需额外判断 - 若需支持自定义序(如按绝对值排序),得自己实现比较逻辑,不能无脑依赖
std::min
断言(assert)只用于调试,不能替代运行时检查
有人喜欢在构造函数里写 assert(start ,这在 debug 模式下有用,但 release 下会被剔除——一旦上线就失去保护,非常危险。
断言只该用于“理论上绝不可能发生”的条件,比如算法中间状态;而 start > end 是现实里高频出现的输入错误,必须走运行时分支。
- 线上环境禁用
assert校验关键约束,这是硬性原则 - 如果性能极度敏感且输入绝对可信(如纯内部枚举生成),才考虑用
assert替代异常,但仍需注释说明前提 - 别为了省一行
throw而用assert掩盖设计漏洞
真正难的不是写那几行校验代码,而是决定“谁负责保证”以及“在哪一层拦截”。构造函数是第一道也是最后一道可靠防线,其他地方都是补丁。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











