C++ note (12)
Eiaton

多态 Ⅱ

抽象类

纯虚函数与抽象类

抽象类语法与语义

  • 能仅能作为基类指针或引用,因为不可能存在抽象对象
  • 不能申明抽象类的对象。例如:Animal a
  • 不能被显式转为抽象类对象。例如:(Animal)dog
  • 不能作为函数参数类型或者返回的值。例如:func(Animal)
  • 能申明为指针或引用,指代自己派生类对象

为多态而生!

抽象类与接口

C++没有接口的概念和定义。

Java接口是由一组函数申明和静态成员构成的特殊类。

C++可以模仿Java的定义描述接口产生类似的效果

  1. 成员函数都是纯虚类
  2. 可以包含静态成员,不包含任何自己的数据
  3. 有虚析构函数,但不需要构造函数
  4. 被虚继承保证唯一,多继承也不会命名冲突

接口的优势:

  • 被继承或被多重继承的派生类必须覆盖实现它的成员
  • 它自己没有数据和业务逻辑,可以无歧义地被多重继承
  • 即使没有任何成员,也可作为任意类型对象的特征。例如:SYSUer 抽象类可作为与中大有时空交集的人的共有特征,如学生,老师,毕业生,进修生 …

虚函数没有定义是链接错误。

虚析构

你可能注意到,程序回避了 new 包含虚函数的类 这样的语句,因为

  • 在抽象类与接口案例中,多态类型指针必须动态转换为对象实际类型指针才能正确执行对象析构过程。
  • EX1:修改 Abstract_type_interface.cpp,取消 dynamic_cast 观察对象是否被正确析构

C++提供了虚析构函数,解决这个问题

  • 虽然析构函数是不继承的,但若基类声明其析构函数为 virtual,则派生的析构函数始终覆盖它。这使得可以通过指向基类的指针 delete 动态分配的多态类型对象
  • EX2:在包含虚函数的基类添加虚析构函数,编译执行,观察多态指针析构过程
    任何包含虚函数的基类的析构函数必须为公开且虚,或受保护且非虚。否则很容易导致内存泄漏

RTTI运行时类型识别机制

RTTI (Run Time Type Identification) 即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。

  • C 是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。
  • 面向对象程序设计中多态性要求,C++中的指针或引用本身的类型,可能与它实际指代(指向或引用)的类型并不一致,需要在运行时将一个多态指针转换为其实际指向对象的类型。

RTTI 提供了两个非常有用的操作符:typeid 和 dynamic_cast。

  • typeid 操作符,返回指针和引用所指的实际类型(type_info 对象);
  • dynamic_cast 操作符,将基类类型的指针或引用安全地转换为其派生类类型的指针或引用。

关键词:typeid 运算符

查询类型的信息。用于必须知晓多态对象的动态类型的场合以及静态类型鉴别。

  • 在使用 typeid 前,必须包含头文件
  • typeid 返回 std::type_info 对象,它常用的有 ==、!= 运算符 和 name() 成员

语法1:typeid ( 类型 )

  • 指代一个表示 类型 类型的 std::type_info 对象。若 类型 为引用类型,则结果所指代的 std::type_info 对象表示被引用的类型。。

语法2:typeid ( 表达式 )

  • 若 表达式 为标识某个多态类型(即声明或继承至少一个虚函数的类)对象的泛左值表达式,则 typeid 表达式对该表达式求值,然后指代表示该表达式动态类型的 std::type_info 对象。
  • 若 表达式 不是多态类型的泛左值表达式,则 typeid 不对该表达式求值,而是由编译静态推导表达式静态类型的 std::type_info 对象

typeid 仅对多态指针表达式求值。其他情况都在编译期完成,其结果可以作为 const 常量表达式。

具体案例请见:https://qingcms.gitee.io/cppreference/20210212/zh/cpp/language/typeid.html

高难度C++面试题,往往出在 sizeof 和 typeid 的表达式是否求值等知识点。

关键词:dynamic_cast 类型转换运算符

沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。

语法:dynamic_cast < 新类型 > ( 表达式 )

若转型成功,则 dynamic_cast 返回 新类型 类型的值。
若转型失败,

  • 且 新类型 是指针类型,则它返回该类型的空指针。
  • 且 新类型 是引用类型,则它抛出与类型 std::bad_cast 的处理块匹配的异常。

侧向是中高级理论题和面试题。

指针和引用不同行为,是理论题的常见考点。

注意,抽象基类一般有虚析构

关键字:final(C++11)

指定某个虚函数不能在子类中被覆盖,或者某个类不能被子类继承。官方例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Base {
virtual void foo();
};
struct A : Base {
void foo() final; // Base::foo 被覆盖而 A::foo 是最终覆盖函数
void bar() final; // 错误: bar 不能为 final 因为它非虚
};

struct B final : A { // struct B 为 final
void foo() override; // 错误:foo 不能被覆盖,因为它在 A 中是 final
};

struct C : B { // 错误:B 为 final
};

多态的实现原理