必须用 class 封装:当变量成组依赖、存在不变量约束(如时间范围校验)、需隐藏实现细节、控制访问权限或管理资源生命周期(raii)时,struct 无法提供构造/析构、访问控制与行为绑定等保障。

当多个变量总是成组出现、相互依赖,且需要统一初始化、约束访问或绑定行为时,就必须用 class 封装,而不是靠零散变量或 struct 硬凑。
数据之间存在不变量约束
比如一个表示时间的结构,hour 必须在 0–23 之间,minute 在 0–59 之间。如果用裸 struct 或三个独立 int,任何地方都能随意赋值,很容易破坏逻辑一致性。
这时应把校验逻辑收进 class 的构造函数和 setter 中:
class Time {
int h_, m_, s_;
public:
Time(int h, int m, int s) : h_(h), m_(m), s_(s) {
if (h 23 || m 59 || s 59)
throw std::invalid_argument("invalid time");
}
void set_hour(int h) { /* 同样校验 */ }
};
- 裸
struct无法阻止外部直接写t.h_ = 999 - 类内校验只写一次,所有实例自动受控
- 若用
struct加 public 成员,约束就形同虚设
需要隐藏实现细节或控制访问权限
例如一个缓存类,内部用 std::unordered_map 存数据,但用户只需调用 get() 和 put(),不该也不必知道底层是哈希表还是 LRU 链表。
class 天然支持 private 成员和 public 接口分离;而 struct 默认 public,强行用 private: 块反而违背直觉,容易被忽略或误用。
- 暴露
std::vector成员会让用户绕过你的容量管理逻辑 - 把成员设为
private后,必须提供明确的接口,边界清晰 - 后续想换存储结构(比如从
vector改成deque),只要接口不变,调用方完全无感
对象生命周期需要定制化管理
一旦涉及资源——文件句柄、动态内存、网络连接、互斥锁——就必须靠 class 的构造/析构函数来保证 RAII。
比如一个日志文件包装器:
class LogFile {
FILE* fp_;
public:
LogFile(const char* path) : fp_(fopen(path, "a")) {
if (!fp_) throw std::runtime_error("cannot open log");
}
~LogFile() { if (fp_) fclose(fp_); }
void write(const char* msg) { fputs(msg, fp_); }
};
- 用
struct无法自动触发fclose,极易泄漏 - 不写析构函数,或忘了
delete,C++ 不会帮你兜底 - 移动语义(
move constructor/move assignment)也只在class场景下有实际意义
真正容易被忽略的是:不是“能不能用 struct”,而是“别人改你代码时,会不会无意中破坏你的假设”。只要存在状态依赖、资源管理或未来可能扩展行为,class 就不是过度设计,而是必要防线。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











