在面向对象编程中,继承就像生物界的遗传机制。想象一下,你从父母那里继承了某些特征(如眼睛颜色),同时又发展出自己独特的个性。这就是继承的核心思想——在保留基类特性的同时进行扩展。
让我们看一个典型场景:校园管理系统中的Student和Teacher类。在未使用继承时,两个类中存在大量重复字段:
cpp复制// 冗余版本
class Student {
protected:
string _name; // 重复字段
string _address; // 重复字段
// ...其他重复字段
};
class Teacher {
protected:
string _name; // 重复字段
string _address; // 重复字段
// ...其他重复字段
};
这种重复不仅增加维护成本,更违反了DRY(Don't Repeat Yourself)原则。当需要修改公共字段时,必须在多个地方同步更改,极易出错。
通过提取公共成员到Person基类,派生类只需关注特有成员:
cpp复制class Person { // 基类
protected:
string _name;
string _address;
// ...其他公共字段
};
class Student : public Person { // 派生类
protected:
int _stuid; // 特有字段
};
class Teacher : public Person { // 派生类
protected:
string _title; // 特有字段
};
关键优势:当需要修改人员基本信息(如增加邮箱字段),只需在Person类中修改一次,所有派生类自动获得更新。
继承方式决定了基类成员在派生类中的可见性,具体规则如下表:
| 基类成员访问权限 | public继承后 | protected继承后 | private继承后 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可见 | 不可见 | 不可见 |
cpp复制class Base {
public:
int x;
protected:
int y;
private:
int z;
};
class Derived : public Base {
// x仍是public
// y仍是protected
// z不可访问
};
cpp复制class Derived : protected Base {
// x变为protected
// y仍是protected
// z不可访问
};
cpp复制class Derived : private Base {
// x变为private
// y变为private
// z不可访问
};
实际经验:除非有特殊设计需求,否则始终使用public继承。其他继承方式会破坏"is-a"关系,导致代码难以理解和维护。
C++中继承方式默认值取决于类定义关键字:
cpp复制class B : A {}; // 默认private继承(危险!)
struct D : C {}; // 默认public继承
强烈建议显式指定继承方式,避免意外行为:
cpp复制class B : public A {}; // 正确做法
派生类作用域嵌套在基类作用域内,形成层级结构。名称查找从派生类向基类逐级向上搜索。
cpp复制class Base {
public:
void func() { cout << "Base::func" << endl; }
};
class Derived : public Base {
public:
void func(int) { cout << "Derived::func" << endl; }
};
int main() {
Derived d;
d.func(1); // 正确:调用Derived::func(int)
d.Base::func(); // 正确:显式指定基类版本
// d.func(); // 错误:名称隐藏发生
}
当派生类定义了与基类同名的成员(无论参数是否相同),基类版本都会被隐藏。这是许多初学者容易踩的坑。
解决方案:
cpp复制class Derived : public Base {
public:
using Base::func; // 引入基类版本
void func(int) { /*...*/ }
};
cpp复制d.Base::func();
考虑以下类结构:
cpp复制class Base {
int x;
};
class Derived : public Base {
int y;
};
内存布局示意:
code复制+---------------+
| Base部分 |
| int x |
+---------------+
| Derived扩展部分 |
| int y |
+---------------+
当派生类对象赋值给基类对象时,会发生切片——只复制基类部分:
cpp复制Derived d;
Base b = d; // 切片发生,丢失Derived特有成员
关键认识:切片是值语义下的自然行为。要避免切片,应使用指针或引用:
cpp复制Base* pb = &d; // 正确:通过指针保持多态性
Base& rb = d; // 正确:通过引用保持多态性
继承机制同样适用于模板类,这是STL中常用的技术:
cpp复制template<typename T>
class Stack : private std::vector<T> { // 私有继承实现适配器
public:
void push(const T& val) {
this->push_back(val); // 注意this->语法
}
T top() const {
return this->back();
}
};
模板基类中的名称在派生类中不会自动可见,需要通过:
模板派生可以特化基类行为:
cpp复制template<>
class Stack<bool> { // 对bool类型的特化版本
// 特殊实现...
};
派生类必须能够完全替代基类,这是public继承的核心契约。违反此原则的典型表现:
当出现以下情况时,应考虑使用组合而非继承:
cpp复制// 使用组合的例子
class Window {
// 基础窗口功能
};
class WindowWithScroll {
Window window;
ScrollBar scrollBar;
// 通过组合扩展功能
};
明确区分继承的目的:
cpp复制void process(Base b) { /*...*/ }
Derived d;
process(d); // 发生切片
调试方法:
cpp复制class A { int data; };
class B : public A {};
class C : public A {};
class D : public B, public C {}; // 两个data副本!
// 解决方案:虚继承
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // 单个data副本
构造顺序原则:
析构顺序完全相反。
虚函数调用涉及:
典型开销:
优化策略:
每个含有虚函数的类会增加一个vptr(通常4/8字节)。多重继承可能引入多个vptr。
示例:
cpp复制class A { virtual ~A(); }; // +vptr
class B { virtual ~B(); }; // +vptr
class C : public A, public B {}; // 两个vptr
C++11引入的关键字:
cpp复制class Derived : public Base {
void foo() override; // 显式标记重写
void bar() final; // 禁止进一步重写
};
优势:
C++11允许构造函数调用同类的其他构造函数:
cpp复制class Derived : public Base {
Derived(int x) : Base(x) {}
Derived() : Derived(0) {} // 委托构造
};
C++11允许继承基类构造函数:
cpp复制class Derived : public Base {
using Base::Base; // 继承所有基类构造函数
};
cpp复制class Base {
public:
void execute() { // 非虚
pre_process();
do_execute(); // 虚
post_process();
}
private:
virtual void do_execute() = 0;
};
cpp复制class NotInheritable final { /*...*/ };
cpp复制class Algorithm {
public:
void run() { // 模板方法
init();
process(); // 由子类实现
cleanup();
}
protected:
virtual void process() = 0;
};
class ConcretAlgo : public Algorithm {
protected:
void process() override { /*...*/ }
};
cpp复制class SortStrategy {
public:
virtual void sort(vector<int>&) = 0;
};
class QuickSort : public SortStrategy { /*...*/ };
class MergeSort : public SortStrategy { /*...*/ };
class Context {
SortStrategy* strategy;
public:
void setStrategy(SortStrategy* s) { strategy = s; }
void execute() { strategy->sort(data); }
};
cpp复制class Stream {
public:
virtual void write(string) = 0;
};
class FileStream : public Stream { /*...*/ };
class Decorator : public Stream {
Stream* stream;
public:
Decorator(Stream* s) : stream(s) {}
void write(string s) override { stream->write(s); }
};
class CryptoStream : public Decorator {
public:
void write(string s) override {
s = encrypt(s);
Decorator::write(s);
}
};
cpp复制// 工厂函数示例
extern "C" Base* createDerived() {
return new Derived;
}
cpp复制void process(Object& obj, Tag tag) {
switch(tag) {
case TAG_A: static_cast<A&>(obj).process(); break;
case TAG_B: static_cast<B&>(obj).process(); break;
}
}
cpp复制struct Obj {
void (*process)(Obj*);
};
void a_process(Obj*) { /*...*/ }
void b_process(Obj*) { /*...*/ }
cpp复制class alignas(64) CacheAlignedBase { /*...*/ };
奇异递归模板模式:
cpp复制template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
void implementation() { /*...*/ }
};
C++20引入的概念(concept):
cpp复制template<typename T>
concept Drawable = requires(T t) {
{ t.draw() } -> std::same_as<void>;
};
template<Drawable T>
void render(T&& obj) {
obj.draw();
}
使用继承实现类型特征检测:
cpp复制template<typename T>
struct is_pointer : std::false_type {};
template<typename T>
struct is_pointer<T*> : std::true_type {};
基于策略的设计:
cpp复制template<typename LockPolicy>
class ThreadSafeQueue : private LockPolicy {
void push() {
typename LockPolicy::Lock guard(this);
// ...
}
};
class MutexPolicy { /*...*/ };
class SpinLockPolicy { /*...*/ };
code复制(gdb) ptype obj
code复制(gdb) p /a *(void***)obj
code复制(gdb) break *(*((void***)obj)[0] + offset)
重构信号:
示例重构:
cpp复制// 重构前
class Button : public Clickable, public Drawable {};
// 重构后
class Button {
ClickBehavior click;
DrawBehavior draw;
};
cpp复制struct S {
void f(this S& self);
};
cpp复制virtual void process() [[expects: x>0]];
cpp复制void draw(auto&& shape) {
inspect(shape) {
Circle c => c.draw();
Rectangle r => r.draw();
}
}
在实际工程中,我发现这些原则特别重要:
一个典型的错误案例:曾在一个网络协议栈实现中,过度使用多层继承导致:
重构为组合+策略模式后,代码更灵活且性能提升30%。这印证了"组合优于继承"的智慧。