接口边界类的功能由用户需求决定,需明确调用者、使用方式和错误约束;公有函数应覆盖全部合法场景且命名体现意图;私有成员变量几乎总是必需的;public接口越少越可控;protected仅用于明确需被派生类复用的场景。

从用户需求倒推接口边界
类对外提供的功能不是凭空设计的,而是由「谁要用、怎么用、不能出什么错」决定的。比如写一个 BankAccount 类,外部调用者只关心“存钱”“取钱”“查余额”,那 deposit()、withdraw()、getBalance() 就是必须暴露的公有函数;而账户编号生成逻辑、内部余额校验规则、数据库写入步骤这些,全得藏在 private 里。
常见错误是把“我方便实现”当成“用户需要”:比如直接把 balance_ 设为 public,让外部自己加减——这等于放弃对资金一致性的控制,一并发操作就出问题。
- 先列出所有合法使用场景(如“用户充值后余额不能为负”“转账需双方账户都存在”)
- 每个场景对应一个公有函数,函数名要反映意图(
transferTo()比modifyBalance()更准确) - 拒绝暴露任何可能破坏对象状态的操作(例如不提供
setBalance(double),除非你明确允许绕过业务规则)
private 成员变量几乎总是必需的
C++ 类中成员变量默认就是 private,但新手常忽略这点,或主动改成 public图省事。这是封装失效的起点。
比如 Rectangle 类里,width 和 height 必须是 private:否则外部能直接赋值负数,导致面积计算出错,且无法触发单位换算、日志记录等配套逻辑。
- 所有数据成员优先声明在
private:区域下 - 如果真需要读取,提供
const成员函数(如getWidth() const),而不是开放变量本身 - 连
struct也别例外——虽然它的默认访问权限是public,但只要涉及状态管理,就该显式写private:
public 函数不是越多越好,而是越少越可控
一个类暴露 12 个 public 函数,往往意味着职责过重或边界模糊。真正健壮的封装,是用最少的接口覆盖全部合法操作。
以线程安全缓冲区 CThreadSafeBuffer 为例:它只暴露 produce()、consume()、size()、isEmpty() 四个函数。没有 clear(),因为清空会破坏生产者-消费者协议;没有 getDataPtr(),因为裸指针会绕过锁保护。
- 每个
public函数都应有明确的前置条件(如“仅当非空时可调用consume()”)和后置保证(如“调用后size()减一”) - 避免提供“辅助型”公有函数(如
validateInternalState()),这类函数只对调试有意义,不该进入接口契约 - 构造函数和析构函数也算
public接口的一部分,它们的参数和异常规范必须和类的使用方式对齐
protected 不是“半公开”,而是为继承留的窄通道
protected 的常见误用是把它当作“比 private 松一点的 public”。实际上,它只在明确需要被派生类复用时才存在,比如基类提供通用的序列化框架,但具体字段序列化逻辑由子类实现。
绝大多数情况下,你不该为封装设计预留 protected 接口。一旦用了,就得承担继承链上所有子类都能修改内部状态的风险。
- 除非你正在设计一个被继承的基类(如
Shape),且已确认子类必须重写某个行为(如calculateArea()),否则不要用protected -
protected成员变量比protected函数更危险——它让子类可以直接读写状态,绕过父类的所有校验逻辑 - 现代 C++ 更倾向用
private+friend或策略类替代protected,把访问权收得更紧
public 函数一旦发布,就很难删改——哪怕它设计得再不合理。所以第一次定义 public 成员时,多花两分钟想清楚调用方的真实诉求,远比后期加兼容层划算。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











