override和final说明符,以及协变返回类型
为了解决继承的一些常见挑战,C ++ 11为C ++添加了两个特殊标识符:override和final。请注意,这些标识符不被视为关键字 - 它们是在某些上下文中具有特殊含义的普通标识符。
虽然final不是很常用,但是override是一个非常棒的补充,你应该使用。在本课中,我们将看一下虚拟函数override返回类型必须匹配的规则的一个例外。
override说明符
正如我们在上一课中提到的,如果派生类虚函数的签名和返回类型完全匹配,则它只被视为重写。这可能导致无意的问题,其中一个旨在实现覆盖的功能实际上不是。
请考虑以下示例:
class A
{
public:
virtual const char* getName1(int x) { return "A"; }
virtual const char* getName2(int x) { return "A"; }
};
class B : public A
{
public:
virtual const char* getName1(short int x) { return "B"; } // 注意:参数是一个短整数
virtual const char* getName2(int x) const { return "B"; } // 注意:函数是const
};
int main()
{
B b;
A &rBase = b;
std::cout << rBase.getName1(1) << '\n';
std::cout << rBase.getName2(2) << '\n';
return 0;
}
因为rBase是对B对象的A引用,所以这里的意图是使用虚函数来访问B :: getName1()和B :: getName2()。但是,因为B :: getName1()采用不同的参数(short int而不是int),所以它不被视为A :: getName1()的重写。更隐蔽的是,因为B :: getName2()是const而A :: getName2()不是,所以B :: getName2()不被认为是A :: getName2()的重写。
因此,该程序打印:
A
A
在这种特殊情况下,因为A和B只是打印它们的名字,所以很容易看出我们搞砸了我们的override,并且调用了错误的虚函数。但是,在一个更复杂的程序中,函数具有行为或返回未打印的值,这些问题可能很难调试。
为了帮助解决那些意图override但不是override的函数的问题,C ++ 11引入了覆盖说明符。通过将说明符放在const所在的相同位置,可以将override应用于任何override函数。如果函数未override基类函数,则编译器会将该函数标记为错误。
class A
{
public:
virtual const char* getName1(int x) { return "A"; }
virtual const char* getName2(int x) { return "A"; }
virtual const char* getName3(int x) { return "A"; }
};
class B : public A
{
public:
virtual const char* getName1(short int x) override { return "B"; } // 编译错误,函数不是覆盖
virtual const char* getName2(int x) const override { return "B"; } // 编译错误,函数不是覆盖
virtual const char* getName3(int x) override { return "B"; } // 可以,函数是A :: getName3(int)的重写
};
int main()
{
return 0;
}
上面的程序产生两个编译错误:一个用于B :: getName1(),另一个用于B :: getName2(),因为它们都不会override先前的函数。B :: getName3()会override A :: getName3(),因此不会为该行生成错误。
使用override说明符没有性能损失,它有助于避免意外错误。因此,我们强烈建议您将其用于您编写的每个虚函数override,以确保您实际上override了您认为的函数。
规则:将override说明符应用于您编写的每个预期override函数。
final说明符
在某些情况下,您可能不希望某人能够override虚拟函数或从类继承。最终说明符可用于告诉编译器强制执行此操作。如果用户尝试覆盖已指定为final的函数或类,则编译器将给出编译错误。
在我们想要限制用户覆盖函数的情况下,最后的说明符在override指定符的相同位置使用,如下所示:
class A
{
public:
virtual const char* getName() { return "A"; }
};
class B : public A
{
public:
// 注意在下一行使用最终说明符 - 这使得该函数不再可覆盖
virtual const char* getName() override final { return "B"; } // 可以,覆盖A :: getName()
};
class C : public B
{
public:
virtual const char* getName() override { return "C"; } // 编译错误:覆盖B :: getName(),这是最终的
};
在上面的代码中,B :: getName()重写了A :: getName(),这很好。但是B :: getName()具有最终说明符,这意味着该函数的任何进一步覆盖都应被视为错误。事实上,C :: getName()尝试覆盖B :: getName()(此处与final说明符不相关,它只是用于良好实践),因此编译器将给出编译错误。
在我们想要阻止从类继承的情况下,在类名后面应用final说明符:
class A
{
public:
virtual const char* getName() { return "A"; }
};
class B final : public A // 注意:在这里使用最终说明符
{
public:
virtual const char* getName() override { return "B"; }
};
class C : public B // 编译错误:无法从最终类继承
{
public:
virtual const char* getName() override { return "C"; }
};
在上面的例子中,B类被声明为final。因此,当C尝试从B继承时,编译器将给出编译错误。
协变返回类型
有一种特殊情况,派生类虚函数override可以具有与基类不同的返回类型,仍然被视为匹配覆盖。如果虚函数的返回类型是指针或对类的引用,则override函数可以返回指针或对派生类的引用。这些被称为协变返回类型。这是一个例子:
#include <iostream>
class Base
{
public:
// 此版本的getThis()返回指向Base类的指针
virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }
void printType() { std::cout << "returned a Base\n"; }
};
class Derived : public Base
{
public:
// 通常,覆盖函数必须返回与基函数相同类型的对象
// 但是,因为Derived是从Base派生的,所以可以返回Derived *而不是Base *
virtual Derived* getThis() { 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(); // 调用Derived :: getThis(),返回Derived *,调用Derived :: printType
b->getThis()->printType(); //调用Derived :: getThis(),返回Base *,调用Base :: printType
}
这打印:
called Derived::getThis()
returned a Derived
called Derived::getThis()
returned a Base
请注意,某些较旧的编译器(例如Visual Studio 6)不支持协变返回类型。
关于协变返回类型的一个有趣的注意事项:C ++无法动态选择类型,因此您将始终获得与被调用函数的基本版本匹配的类型。
在上面的例子中,我们首先调用d.getThis()。由于d是Derived,因此调用Derived :: getThis(),它返回Derived *。然后,此Derived *用于调用非虚函数Derived :: printType()。
现在有趣的案例。然后我们调用b-> getThis()。变量b是指向派生对象的Base指针。Base :: getThis()是虚函数,因此调用Derived :: getThis()。虽然Derived :: getThis()返回Derived *,但由于函数的基本版本返回Base *,返回的Derived *将向下转换为Base *。因此,调用Base :: printType()。
换句话说,在上面的示例中,如果您使用首先键入为Derived对象的对象调用getThis(),则只能获得Derived *。