封装的核心标准是所有数据成员必须声明为private:若存在public或protected的成员变量,或setter未做校验、返回非常量引用、允许绕过控制逻辑,则封装失败;编译期必须阻止直接访问私有成员。

直接看成员变量是否被设为 private
封装不是“写了 class 就算”,核心判断标准就一条:所有数据成员(属性)是否默认或显式声明为 private。只要有一个 public 成员变量,比如 int age; 或 std::string name; 直接暴露在 public: 区域下,那这个类就**没做到基本封装**。
常见错误现象:
- 用
struct定义类,却没改默认权限(struct默认是public,而class默认是private) - 把 setter/getter 写全了,但忘了把底层变量本身设为
private,导致用户仍可绕过函数直接读写:s1.age = -5;依然合法 - 用
protected代替private,误以为“子类不能访问外部就不能乱改”——但protected对派生类开放,且容易被滥用,不等于封装
public 区域里只允许出现行为,不允许出现裸数据
真正封装的类,public: 段里应该只有函数声明(构造、析构、setter、getter、业务方法),绝不能有非静态数据成员。哪怕这个变量看起来“只读”,比如 const int MAX_SIZE;,只要它出现在 public 下,外部就能直接取地址、绑定引用、甚至通过模板元编程窥探,破坏封装边界。
正确做法:
- 所有数据成员统一放在
private:块开头 - 如需对外提供只读访问,用
const返回值的 getter:int getAge() const { return m_age; },而不是暴露m_age本身 - 避免返回内部成员的非常量引用或指针(比如
std::string& getName()),否则调用方能绕过控制逻辑直接修改
检查 setter 是否做了有效校验,而非形同虚设
光有 setAge(int a) 不代表封装到位。如果函数体里只是 m_age = a;,那和直接赋值 s.m_age = a;(假设 m_age 是 public)没本质区别——只是多敲几个字符而已。
一个合格的 setter 应该体现“控制权在类内”:
- 对输入做范围/合法性检查,比如
if (a 150) throw std::invalid_argument("age out of range"); - 隐式转换或归一化,比如把传入的
" abc "自动 trim 后存入m_name - 触发副作用,比如修改时间戳、通知观察者、更新缓存等——这些逻辑一旦脱离 setter,就说明封装被破坏
反例:void setAge(int a) { m_age = a; } —— 这叫“包装式裸露”,不是封装。
编译期能否阻止非法访问是最硬的检验标准
真正的封装必须能在编译阶段报错,而不是靠文档或约定。你可以随手写段测试代码验证:
Student s; s.m_age = 42; // 如果这行能编译通过 → 封装失败 s.setAge(42); // 这行能过,但上一行必须失败 → 才算过关
注意:某些 IDE 或旧编译器可能对友元、模板特化、ADL 等机制放宽检查,但标准 C++ 要求严格。只要 m_age 是 private,且没被声明为友元,任何类外直接访问都会触发 error: 'int Student::m_age' is private。
容易被忽略的点:嵌套类、lambda 捕获、返回 auto 类型时,可能意外暴露底层实现细节;只要外部能通过类型推导拿到私有成员的引用或指针,封装就算漏了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











