MST

星途 面试题库

面试题:C++继承访问规则在模板与运行时多态中的深度应用

考虑一个C++模板类 `TemplateClass`,它以不同继承方式(公有、私有、保护)继承自一个基类 `BaseClass`,且 `BaseClass` 包含虚函数。编写一个函数模板,该函数模板接收 `TemplateClass` 类型对象,并调用 `BaseClass` 的虚函数。请详细说明在不同继承方式下,如何确保虚函数的正确调用以及运行时多态的实现,同时分析编译器在处理这种情况时的工作原理和潜在优化策略。
32.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

代码实现

#include <iostream>

class BaseClass {
public:
    virtual void virtualFunction() {
        std::cout << "BaseClass::virtualFunction" << std::endl;
    }
};

// 公有继承的TemplateClass
template <typename T>
class TemplateClassPublic : public BaseClass {
public:
    void someFunction() {
        // 类内操作
    }
};

// 私有继承的TemplateClass
template <typename T>
class TemplateClassPrivate : private BaseClass {
public:
    void someFunction() {
        // 类内操作
    }
    // 提供公有接口调用虚函数
    void callVirtualFunction() {
        BaseClass::virtualFunction();
    }
};

// 保护继承的TemplateClass
template <typename T>
class TemplateClassProtected : protected BaseClass {
public:
    void someFunction() {
        // 类内操作
    }
    // 提供公有接口调用虚函数
    void callVirtualFunction() {
        BaseClass::virtualFunction();
    }
};

// 函数模板
template <typename T>
void callVirtualFunc(T& obj) {
    obj.virtualFunction();
}

不同继承方式下确保虚函数正确调用及运行时多态实现

  1. 公有继承
    • 在公有继承下,TemplateClassPublicBaseClass 公有继承,BaseClass 的公有和保护成员在 TemplateClassPublic 中保持原有访问权限。
    • 函数模板 callVirtualFunc 可以直接调用 obj.virtualFunction(),因为 virtualFunctionTemplateClassPublic 中是公有的,对象可以直接访问。
    • 运行时多态通过虚函数表(vtable)实现。当创建 TemplateClassPublic 对象时,会为其分配一个虚函数表指针(vptr),指向 TemplateClassPublic 对应的虚函数表。在运行时,根据对象实际类型(即 TemplateClassPublic)的虚函数表来调用正确的虚函数。
  2. 私有继承
    • 私有继承下,BaseClass 的所有成员在 TemplateClassPrivate 中变为私有成员。
    • 直接在 callVirtualFunc 中调用 obj.virtualFunction() 会导致编译错误,因为 virtualFunctionTemplateClassPrivate 中是私有的,外部无法访问。
    • 为了确保虚函数的正确调用,可以在 TemplateClassPrivate 中提供一个公有接口函数(如 callVirtualFunction),在该函数内部调用 BaseClass::virtualFunction()。然后在 callVirtualFunc 函数模板中调用 obj.callVirtualFunction()
    • 运行时多态同样通过虚函数表实现,尽管访问权限改变,但虚函数机制依然生效。
  3. 保护继承
    • 保护继承时,BaseClass 的公有和保护成员在 TemplateClassProtected 中变为保护成员。
    • 直接在 callVirtualFunc 中调用 obj.virtualFunction() 会失败,因为 virtualFunction 是保护的,外部无法访问。
    • 类似私有继承,需要在 TemplateClassProtected 中提供一个公有接口函数(如 callVirtualFunction)来调用 BaseClass::virtualFunction(),然后在 callVirtualFunc 函数模板中调用 obj.callVirtualFunction()
    • 运行时多态基于虚函数表机制正常工作。

编译器工作原理和潜在优化策略

  1. 编译器工作原理
    • 编译器在编译时,会为包含虚函数的类(如 BaseClass)生成虚函数表。虚函数表是一个数组,存储类中虚函数的地址。
    • 当类继承自含有虚函数的基类时,编译器会根据继承方式调整虚函数表。例如,在公有继承中,子类的虚函数表会继承基类虚函数表,并根据需要重写虚函数地址。
    • 在对象创建时,编译器会为对象分配一个虚函数表指针(vptr),指向对应的虚函数表。
    • 当调用虚函数时,编译器生成的代码通过对象的 vptr 找到虚函数表,再根据虚函数表中的地址调用实际的虚函数。
  2. 潜在优化策略
    • 内联虚函数:如果虚函数的实现比较简单,编译器可能会对其进行内联优化,直接将函数代码嵌入调用处,避免虚函数表查找的开销。
    • 虚函数表指针优化:对于一些简单的继承结构,编译器可以在编译时确定虚函数的地址,从而避免运行时通过虚函数表查找,实现静态绑定优化。
    • Devirtualization:在某些情况下,编译器通过分析代码,发现对象的实际类型在编译时可知,就可以将虚函数调用优化为普通函数调用,消除虚函数表查找的开销。