Skip to content

13 多态

抽象类

使用 = 0 声明纯虚函数,包含纯虚函数的类是抽象类不能实例化

  • 不能申明抽象类的对象。例如:Animal a
  • 不能被显式转为抽象类对象。例如:(Animal)dog
  • 不能作为函数参数类型或者返回的值。例如:func(Animal)
  • 能申明为指针或引用,指代自己派生类对象
class Shape {
public:
    virtual double area() const = 0;  // 纯虚函数
};

class Circle : public Shape {
public:
    double area() const override { return 3.14 * m_radius * m_radius; }
};

纯虚函数可以(不建议)提供定义(实现),但定义必须在类外提供。

如果基类析构函数是纯虚的,必须提供定义(哪怕空实现),因为当派生类对象被销毁时,编译器会自动隐式地调用所有基类的析构函数。

虚析构函数

虚析构函数就是在基类中声明为 virtual 的析构函数。它的作用是:确保通过基类指针删除派生类对象时,能正确调用完整的析构链(先派生类析构,再基类析构)。

class Base {
public:
    ~Base() { cout << "Base dtor\n"; } // 非虚析构
};
class Derived : public Base {
public:
    int* data = new int[10]; 
    ~Derived() { delete[] data; cout << "Derived dtor\n"; }
};

int main() {
    Base* ptr = new Derived();
    delete ptr; // 仅输出 "Base dtor",Derived 的 data 发生内存泄漏!
}

如果基类析构不是虚函数:只会调用基类析构,子类析构不执行,子类资源泄漏!

class Base {
public:
    virtual ~Base() { cout << "Base dtor\n"; } // 虚析构
};
// Derived 保持不变...

int main() {
    Base* ptr = new Derived();
    delete ptr; // 正确输出 "Derived dtor" -> "Base dtor",资源安全释放
}

如果基类析构不是虚函数,但是析构时用dynamic_cast转型成子类指针,再delete,也可以安全释放全部资源。

如果基类的析构函数是 protected 且非虚,这种设计通常用于表达一种明确的意图:"这个基类只能作为派生类的一部分存在,禁止通过基类指针从外部销毁对象。" 这在编译阶段就彻底阻断了外部代码通过基类指针 delete 派生类对象的可能,不需要靠 virtual 来"保护"。

RTTI

RTTI(Run-Time Type Identification),即运行时类型识别。它是 C++ 提供的一种机制,允许程序在运行期间动态地查询一个对象的实际派生类型。

RTTI 提供了两个非常有用的操作符:typeiddynamic_cast

typeid

  • 在使用 typeid 前,必须包含头文件 <typeinfo>
  • typeid 返回 std::type_info 对象,它常用的有 ==!= 运算符 和 name() 成员
  • typeid 通常不对表达式求值,仅当操作数是多态类类型的左值时,才会在运行时对表达式求值以获取动态类型信息。
int a = 10;
typeid(a = 100);  // a 不会变成 100

Base* p = new Derived;
typeid(*p);  // 会对 *p 求值,查真实类型
typeid(p);    // 只求指针类型,不求值
typeid(p->f()); // 不调用函数

dynamic_cast

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

dynamic_cast < 新类型 > ( 表达式 )

若转型失败:

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

final说明符 (C++11)

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

  1. 修饰类:禁止被继承

    class Base final { ... }; // Base 类不能被继承
    class Derived : public Base { ... }; // 编译错误!
    
  2. 修饰虚函数:禁止被重写

    class Base {
        virtual void show() final; // 此虚函数不可被重写
    };
    class Derived : public Base {
        void show() override; // 编译错误!(不写override也会编译错误)
    };