一、虚函数与协变返回类型
虚函数是一种特殊类型的成员函数,当被调用时,会根据实际被引用或指向的对象的类型解析到其最派生(最终)版本的函数。
如果一个派生类中的函数与基类版本具有相同的签名(函数名、参数类型、是否为 const)和返回类型,则该派生函数会被认为是匹配的,这样的函数被称为重写(override)。
有一种特殊情况是,派生类的虚函数重写可以具有不同于基类的返回类型,但仍可被视为匹配的重写。
这种情况是,如果虚函数的返回类型是某个类的指针或引用,那么重写函数可以返回指向派生类的指针或引用。这些被称为协变返回类型。
协变返回类型通常用于虚成员函数返回指向包含该成员函数的类的指针或引用的情况(例如下述代码中的Base::getThis()
返回 Base*
,而 Derived::getThis()
返回 Derived*
)。然而,这并非绝对必要,根据定义只要重写成员函数的返回类型是从基类虚成员函数的返回类型派生而来的,就可以使用协变返回类型。
二、静态类型与动态类型
从一个例子开始分析
#include <iostream>
#include <string_view>
class Base
{
public:
// This version of getThis() returns a pointer to a Base class
virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }
void printType() { std::cout << "returned a Base\n"; }
};
class Derived : public Base
{
public:
// Normally override functions have to return objects of the same type as the base function
// However, because Derived is derived from Base, it's okay to return Derived* instead of Base*
Derived* getThis() override { std::cout << "called Derived::getThis()\n"; return this; }
void printType() { std::cout << "returned a Derived\n"; }
};
int main()
{
Derived d{};
Base* b{ &d };
d.getThis()->printType(); // calls Derived::getThis(), returns a Derived*, calls Derived::printType
b->getThis()->printType(); // calls Derived::getThis(), returns a Base*, calls Base::printType
return 0;
}
1、C++ 的类型系统是静态的
- 静态类型(Static Type):变量声明时的类型,在编译期确定。
- 动态类型(Dynamic Type):实际指向的对象类型,在运行期确定。
在Base* b{ &d };
中,Base*
就是b
的静态类型,而Derived*
则是b
的动态类型。
2、虚函数的动态绑定范围
- 动态绑定仅适用于函数体:虚函数的实现代码(函数体)会根据动态类型动态选择。
- 返回类型是静态的:虚函数的返回类型在编译期根据调用表达式的静态类型确定。
virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }
在调用b->getThis()
时,由于Base::getThis()
是虚函数,而Derived::getThis()
是对应的一个override,所以b->getThis()
的函数体实际上是Derived::getThis()
的函数体。但是由于动态绑定仅适用于函数体,而返回类型是静态的,所以b->getThis()
的返回值的静态类型是Base*
,当然此时其对应的实际(动态)类型是Derived*
。
设b->getThis()的临时返回对象为baseRetTmp,其函数体实际的临时返回对象为derivedRetTmp的话,可以简化为下述代码:
Base* baseRetTmp = derivedRetTmp // derivedRetTmp的类型为Derived*
所以开头代码的b->getThis()->printType();
的执行流程很好分析了:
-
b->getThis() 的静态类型是 Base*(尽管实际返回 Derived*)
-
虚函数动态绑定:通过基类指针调用虚函数 getThis(),实际执行 Derived::getThis()
-
返回类型被隐式向上转型为 Base*
-
调用链:Base* -> printType(),由于 printType() 是普通函数,直接使用指针的静态类型查找函数,调用 Base::printType()
-
最后输出:
called Derived::getThis() returned a Base
三、更复杂的例子
#include <iostream>
class A {
public:
void print() { std::cout << "A"; }
virtual void vprint() { std::cout << "A"; } // 虚函数
};
class B : public A {
public:
void print() { std::cout << "B"; } // 隐藏基类函数
void vprint() override { std::cout << "B"; } // 重写虚函数
};
class C {
private:
A m_a{};
public:
virtual A& get() { return m_a; } // 基类返回 A&
};
class D : public C {
private:
B m_b{};
public:
B& get() override { return m_b; } // 协变返回类型:B& 是 A& 的派生类
};
int main() {
D d{};
C& ref = d;
ref.get().vprint(); // 输出 B
}
1、执行过程
阶段 | 关键行为 | 结果 |
---|---|---|
1. ref.get() | 虚函数动态绑定到 D::get() | 返回 m_b (实际类型为 B ,静态类型为 A& ) |
2. .vprint() | 通过 A& 调用虚函数 | 根据实际对象类型 B 调用 B::vprint() |