is-a关系是设计契约而非语法糖,本质是派生类对象能在任何需要基类对象的场景中安全无副作用地替换基类对象;公有继承表达is-a,私有/保护继承则表示实现复用;违反可替换性即is-a滥用。

is-a关系不是语法糖,也不是编译器自动加的标签;它是一个设计契约——只要用了public继承,你就承诺:派生类对象在任何需要基类对象的地方,都能安全、无副作用地替代它。
is-a的本质是可替换性,不是“看起来像”
很多人看到class Dog : public Animal就以为“狗是一种动物”只是语义类比,其实它有严格的运行时含义:
- 基类指针可以指向派生类对象:
Animal* p = new Dog;合法且安全 - 基类引用可以绑定派生类对象:
void feed(Animal& a); feed(dog_instance);不需转型 - 派生类对象能隐式转换为基类对象(切片发生但不报错):
Animal a = dog;—— 这是合法的,但要注意数据丢失 - 如果违反这个契约(比如派生类重写了某个函数却改变了前置条件或后置条件),哪怕语法通过,也属于is-a滥用
公有继承才表达is-a,私有/保护继承不是
private或protected继承在语法上允许,但它们不建立is-a关系:
-
class Stack : private std::vector<t></t>表示“Stack用vector实现”,不是“Stack是一种vector” - 此时
Stack*不能转成std::vector<t>*</t>,连隐式转换都不被允许 - 这种写法本质是“implemented-in-terms-of”,和组合(has-a)语义接近,只是复用方式不同
is-a被破坏的典型信号
以下现象说明你可能误用了继承,is-a正在失效:
- 派生类中大量重写基类虚函数,并在内部抛出
std::logic_error来拒绝某些基类本应支持的操作(比如Circle::rotate()直接报错,而Shape::rotate()本意是支持任意图形旋转) - 为了绕过基类接口限制,在派生类里加一堆
static_cast<derived>(base_ptr)</derived>强转再调用特有函数 - 用户必须先用
dynamic_cast判断类型,才能决定能否调用某个方法 —— 这说明基类接口已无法统一描述行为 - 基类析构函数没声明为
virtual,但又用基类指针去delete派生类对象:行为未定义,is-a在资源管理层面已断裂
真正难的不是写对class B : public A,而是每次加一个新派生类时,都得重新问自己:它是否真的能在所有A出现的上下文中,不做修改、不加条件、不依赖类型检查,就直接工作?这个判断没法靠编译器提醒,只能靠设计时的手动校验。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











