关于继承
概念定义
- 用户
- 把包含这个基类或派生类的第三方类或函数暂时称为用户
- 基类定义如图:
public继承
1 |
class Derived : public Base { ... } |
含义
- 基类的
public
成员在派生类中仍然是public
的 - 基类的
protected
成员在派生类中仍然是protected
的 - 基类的
private
成员在派生类中仍然是不可访问的- 只能通过基类的
public
或protected
成员函数来间接访问
- 只能通过基类的
对用户
- 用户可以通过派生类对象访问基类的
public
成员
适用场景
- 希望继承关系是
“is-a”
关系,并且希望用户能够使用基类的public
接口时使用public
继承
权限如图
protected继承
1 |
class Derived : protected Base { ... } |
含义
- 基类的
public
成员在派生类中变成protected
- 基类的
protected
成员在派生类中仍然是protected
- 基类的
private
成员在派生类中仍然是不可访问的- 只能通过基类的
public
或protected
成员函数来间接访问
- 只能通过基类的
对用户
- 用户不能通过派生类对象访问基类的
public
成员- 因为这些成员在派生类中变成了
protected
成员
- 因为这些成员在派生类中变成了
适应场景
- 当你希望继承关系是
“is-a”
关系- 但不希望基类的
public
接口暴露给用户 - 而只希望在派生类及其子类中使用基类的接口时使用
protected
继承
- 但不希望基类的
权限如图
private继承
1 |
class Derived : private Base { ... } |
含义
- 基类的
public
成员在派生类中变成private
- 基类的
protected
成员在派生类中变成private
- 基类的
private
成员在派生类中仍然是不可访问的- 只能通过基类的
public
或protected
成员函数来间接访问
- 只能通过基类的
对用户
- 用户不能通过派生类对象访问基类的任何成员
- 因为所有基类的成员在派生类中都变成了
private
成员
- 因为所有基类的成员在派生类中都变成了
适应场景
- 当你希望继承关系是
“is-implemented-in-terms-of”
关系- 且不希望基类的接口暴露给用户
- 甚至不希望基类的接口在派生类中公开时使用
private
继承
权限如图
权限的改变
改变
public
继承:- 基类的
public
成员在派生类中保持public
,protected
成员保持protected
- 基类的
protected
继承:- 基类的
public
成员在派生类中变为protected
,protected
成员保持protected
- 基类的
private
继承:- 基类的
public
和protected
成员在派生类中都变为private
。
- 基类的
问题
private
继承中,基类的public
和protected
成员在派生类中都变为了private
,为什么还可以在派生类里面访问这些成员?- 当一个类通过
private
继承另一个类时,基类的public
和protected
成员在派生类中都变为private
这意味着这些成员对于派生类之外的任何代码都是不可见的(即使是从派生类派的派生类也无法访问它们) - 然而,这些成员对于派生类自身的成员函数来说仍然是可访问的
这是因为继承的访问控制只影响从外部访问的权限,而不影响派生类内部的成员函数对基类成员的访问
- 当一个类通过
组合和private
组合
- 优点
- 清晰的语义:组合表达了
“has-a”
关系。例如,汽车有一个发动机 (Car has an Engine
) - 更灵活:组合允许在运行时动态替换组件或成员对象。例如,可以在运行时替换汽车的发动机
- 低耦合度:组合使得类之间的耦合度较低,更容易维护和扩展
- 清晰的接口:组合可以清晰地定义类之间的接口,避免不必要的成员暴露
- 清晰的语义:组合表达了
- 缺点
- 可能需要更多的代码:需要在成员对象上显式调用方法,增加了代码复杂性
- 不能直接访问成员类的保护成员:需要成员类提供合适的接口
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> class Engine { public: void start() { std::cout << "Engine started" << std::endl; } }; class Car { private: Engine engine; public: void startEngine() { engine.start(); } }; int main() { Car car; car.startEngine(); // 组合方式 return 0; } |
private
- 优点
- 简化代码:派生类可以直接访问基类的成员,而不需要额外的代码包装。
- 表达实现细节:
private
继承表达了“is-implemented-in-terms-of”
的关系,可以将基类的实现细节封装在派生类中
- 缺点
- 强耦合:
private
继承导致派生类和基类之间的耦合度较高,如果基类发生变化,派生类也需要相应修改 - 不清晰的接口:基类的实现细节暴露在派生类中,可能导致接口不清晰
- 继承层次复杂:使用多重继承时,
private
继承可能导致继承层次复杂化,增加代码的维护难度
- 强耦合:
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> class Engine { public: void start() { std::cout << "Engine started" << std::endl; } }; class Car : private Engine { public: void startEngine() { start(); } }; int main() { Car car; car.startEngine(); // private 继承方式 return 0; } |
权限总结
-
不管是什么样的继承方式:
- 派生类可以访问基类的
public
成员和函数 - 派生类可以访问基类的
protected
成员和函数 - 派生类不能直接访问基类的
private
成员和函数
- 派生类可以访问基类的
-
对于用户:
- 只有在
public
继承的情况下,用户可以通过派生类访问基类的public
成员
- 只有在
关于实现原理
概述
- 在
C++
中,public
、protected
和private
访问控制是由编译器来实现的 - 这些访问控制机制确保了类的成员在适当的访问权限下使用,防止不当的访问和修改
如何实现
- 语法分析:
- 编译器在解析类定义时,会记录每个成员的访问权限(
public
、protected
或private
)
- 编译器在解析类定义时,会记录每个成员的访问权限(
- 语义检查:
- 在编译过程中,编译器会检查对类成员的每一次访问,根据记录的访问权限进行验证
- 如果发现访问权限违规,编译器会生成编译错误
- 代码生成:
- 在生成目标代码时,编译器确保只有合法的访问会被转换成机器指令,非法访问会在编译阶段被阻止
MSVC
- 符号表和访问控制:
MSVC
在编译期间维护一个符号表,用于存储类的成员信息,包括其类型、名称和访问权限- 在语法分析阶段,编译器会将每个类成员的访问权限信息记录在符号表中
- 访问控制检查:
- 在语义分析阶段,
MSVC
会检查每个成员的访问
如果一个成员的访问权限不匹配当前的访问上下文,编译器会生成一个错误 MSVC
使用特定的错误代码和消息来报告访问权限违规
例如C2248
错误(尝试访问私有成员)
- 在语义分析阶段,
- 代码生成:
- 在通过访问控制检查后,
MSVC
会继续进行代码生成 - 合法的成员访问会被转换为相应的机器指令,不合法的访问会在编译阶段被阻止,因此不会出现在生成的目标代码中
- 在通过访问控制检查后,
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Spdlog记述:四09/16
- ♥ 文件md5值计算05/31
- ♥ 51CTO:Linux C++网络编程二08/14
- ♥ C++17_第一篇12/20
- ♥ STL_deque05/18
- ♥ C++并发编程 _ 基于锁的数据结构08/19