C++经典阅读笔记- Advanced C++ Programming Styles and Idioms
面向对象不是 class:一次对 OOP、ADT 与现代 C++ 的重新认识
很多人以为: 面向对象 = class + 继承 + virtual
但当你写得越久、系统越复杂,就越容易发现: 问题往往不是“不会写类”,而是“抽象从一开始就错了”。
这篇文章想讨论一个常被忽略、却极其关键的问题:
面向对象,到底是一种语言特性,还是一种设计思想?
一、一个常见但危险的误解
在绝大多数教学和实践中,面向对象往往被这样介绍:
- 有
class - 有
private / public - 有继承
- 有多态
久而久之,很多人形成了一个潜意识等式:
“我用了 class,我就在做面向对象设计。”
但事实是:
你完全可以写满 class,却完全没有任何好的面向对象设计。
二、面向对象真正的起点:抽象数据类型(ADT)
在语言出现 class 之前,面向对象的核心思想就已经存在。
它的名字叫:
抽象数据类型(Abstract Data Type, ADT)
ADT 的核心只有三点:
- 数据与操作绑定
- 隐藏实现细节
- 通过明确的接口约束使用方式
注意: 这里没有任何语言关键词。
三、为什么 C 语言也可以写“正统的 OOP”?
看一个非常经典的 C 语言例子:
// stack.h
typedef struct Stack Stack;
Stack* stack_create(void);
void stack_push(Stack*, int);
int stack_pop(Stack*);
void stack_destroy(Stack*);
// stack.c
struct Stack {
int data[100];
int top;
};
这个设计已经满足:
- 用户无法访问内部结构
- 所有操作通过接口完成
- 实现细节完全隐藏
这在设计层面已经是完整的面向对象抽象。
区别只是:
- 在 C 中靠自律
- 在 C++ 中由编译器强制
四、C++ 到底改变了什么?
一个关键结论是:
C++ 并没有发明面向对象,它只是让抽象“更安全、更低成本”。
C++ 提供的是一整套实现 ADT 的语言级工具:
| C++ 特性 | 实际作用 |
|---|---|
class / private |
编译期强制封装 |
| 构造 / 析构 | 生命周期自动管理 |
| RAII | 把资源管理变成类型属性 |
| 模板 | 抽象前移到编译期 |
| Concepts | 显式表达接口契约 |
但请注意:
这些都是“工具”,不是“设计原则”。
五、现代 C++ 的一个重要转向:抽象前移
现代 C++(C++17/20/23)正在做一件非常一致的事情:
- 减少继承
- 减少运行时多态
- 增强编译期抽象
- 强调值语义(value semantics)
例如,用 templates + concepts 描述抽象:
template<typename T>
concept Shape = requires(const T& s) {
{ s.area() } -> std::convertible_to<double>;
};
这本质上是:
用类型系统描述 ADT 的“行为契约”,而不是构造继承层次。
这正好呼应了早期 OOP 理论中的一个观点:
类型系统主要存在于编译期,而不是运行期。
六、为什么“class 是 OOP 核心”的观念如此顽固?
原因并不复杂:
- class 是可见的语法
- 抽象边界是不可见的设计决策
- 教学往往从语法讲起,而非从设计讲起
于是我们学会了:
- “怎么写类”
却很少被教:
- “什么时候不该写类”
- “抽象边界应该画在哪里”
七、一个更准确的三层模型
你可以这样重新理解三者的关系:
- 面向对象:设计范式(语言无关)
- ADT:面向对象的工程基础
- C++:提供多种机制实现 ADT 的系统级语言
因此:
- C 可以写优秀的面向对象系统
- C++ 也可以写灾难级的“伪 OOP”
八、一个简单但有力的判断标准
下次你评估一个“面向对象设计”时,不妨问自己三个问题:
- 抽象是否独立于实现?
- 接口是否稳定,变化是否被隔离?
- 类型是否在帮助你避免错误,而不是制造复杂性?
如果答案是否定的:
那么不论用了多少
class,它都不是好的 OOP。
九、结语
面向对象不是“我用了什么语法”, 而是“我在哪里划清了抽象边界”。
当你意识到这一点时:
- class 会变成可选项
- 继承会变成罕用工具
- 抽象,会重新回到设计的中心
这,才是现代 C++ 与早期 OOP 理论真正的交汇点。
有了上面的思考,应该让我们可以处于非常正确的认知节点上了。


评论