类不变量是对象必须始终满足的状态约束,不是可选建议而是合法性底线,如SimpleVector的_size必须始终≤_capacity且≥0。

类不变量是对象必须始终满足的状态约束
它不是可选的“风格建议”,而是对象合法性的底线。比如 SimpleVector 的 _size 、<code>Date 类的 1 ,一旦被破坏,对象就进入未定义行为区域——后续任何调用都可能崩溃或返回垃圾值。
封装是强制维护不变量的技术前提
没有封装,不变量根本守不住。原因很直接:
- 如果成员变量是
public,外部代码能绕过所有检查直接赋值:v._size = 1000; v._capacity = 10;—— 不变量瞬间失效,且编译器和运行时都不会拦你 - 只有把数据设为
private,才能把修改入口收束到少数几个成员函数里,才可能在push_back、resize等操作中插入校验逻辑 - 构造函数和析构函数也必须是可控的入口:
SimpleVector(size_t cap)里用assert或异常确保初始状态合法;析构前也要保证对象仍处于有效状态(否则delete[] _data可能崩在空指针或野指针上)
断言、异常和私有验证函数是常见守卫手段
它们不是装饰,而是不变量的落地机制:
assert(_size 放在每个公有函数末尾,只在 debug 构建生效,低成本捕获开发期错误- 对用户输入敏感的操作(如
set_day(int d))应抛出std::invalid_argument,而不是静默接受非法值 - 可提取私有成员函数
bool is_valid() const,在关键节点统一调用,避免重复逻辑;但注意别让它成为性能瓶颈(比如在 tight loop 里反复调用)
移动语义会临时削弱强不变量
这是最容易被忽略的复杂点。标准容器(如 std::vector)的移动构造函数允许被移动对象进入“有效但未指定状态”——它的 _data 变成 nullptr,_size 和 _capacity 可能为 0 或任意值,只要析构函数能安全处理就行。这意味着:
立即学习“C++免费学习笔记(深入)”;
- 你不能假设移动后的对象还能调用
size()或data()(除非文档明确保证) - 如果你自己实现移动操作,必须在移动后手动把源对象置为一个可析构、可赋值的最小合法状态,哪怕这个状态比“完整不变量”更宽松
- 测试时要专门覆盖“移动后访问原对象”的场景,否则上线后遇到
segfault才发现,已经晚了


















