虚函数的返回类型应该与基类中的类型相同,或者是协变的。但是为什么我们有这个限制呢?
4 回答
因为随之而来的废话:
struct foo
{
virtual int get() const { return 0; }
};
struct bar : foo
{
std::string get() const { return "this certainly isn't an int"; }
};
int main()
{
bar b;
foo* f = &b;
int result = f->get(); // int, right? ...right?
}
让派生类返回完全不相关的东西是不明智的。
因为使用返回值的代码将如何处理各种返回的不相关类型?例如:
class A
{
public:
virtual float func();
};
class B: public A
{
public:
virtual char *func();
};
A *p = (some_condition) ? new A() : new B();
p->func(); // Oh no! What is the type?
根据 C++ 标准:
覆盖函数的返回类型应与被覆盖函数的返回类型相同或与函数的类协变。如果函数 D::f 覆盖函数 B::f,则函数的返回类型如果满足以下条件,则它们是协变的:
1)两者都是类的指针或类的引用
2) B::f的返回类型中的类与D::f的返回类型中的类是同一类,或者是D的返回类型中的类的明确且可访问的直接或间接基类::F
3)两个指针或引用都具有相同的 cv 限定,并且 D::f 的返回类型中的类类型具有与 B::f 的返回类型中的类类型相同或更少的 cv 限定.
答案与“为什么我不能将向量<Apple*> 分配给向量<Fruit*>?”的答案非常相似。关于 Bjarne Stroustrup 的常见问题解答。
在处理多态类型时,修改返回类型的能力会导致语言的类型安全出现漏洞(有关特定示例,请参见@GManNickG 的答案)。
当影响返回类型时,有一种相当常见的情况是理想的:从基类型的虚方法返回多态指针时。例如,
class Base {
public:
virtual Base* parent() = 0;
};
class Child : public Base {
public:
Base* parent() override
{
return parent_;
}
private:
Parent* parent_; // Assume `Parent` type exists.
};
Child
在这里,我们丢失了知道它的parent_
成员的类型信息。这会导致大量的强制转换,即使类型在某一时刻定义良好。我们可以使用Curiously Recurring Template Parameter (CRTP) 成语来解决这个问题,
template<class ParentType>
class Base {
public:
virtual ParentType* parent()
{
return parent_;
}
private:
ParentType* parent_;
};
class Child : public Base<Parent> {
};