6

以下 C++ 代码在编译时给了我这些错误:

covariant.cpp:32:22: error: invalid covariant return type for ‘virtual Q<B> C::test()’
covariant.cpp:22:22: error:   overriding ‘virtual Q<A> B::test()’

我不想更改该行virtual Q<B> test() {}virtual Q<A> test() {}尽管它消除了编译错误。有没有其他方法可以解决这个问题?

template <class T>
class Q
{
    public:
        Q() {}
        virtual ~Q() {}
};

class A
{
    public:
        A() {}
        virtual ~A() {}    
};

class B
{
    public:
        B() {}
        virtual ~B() {}

        virtual Q<A> test() = 0;

};

class C : public B
{
    public:
        C() {}
        virtual ~C() {}

        virtual Q<B> test() {}
};
4

3 回答 3

10

Q<B>并且Q<A>是不相关的类。想象一下,您是B调用的客户test():如果您不知道结果将具有什么类型,您会将结果分配给什么?

两者Q<A>Q<B>都是同一个类模板的实例这一事实并没有改变它们是两个完全不相关的类的事实,可能具有完全不同的布局(由于模板专业化)。

这与执行以下操作没有什么不同:

struct X
{
    virtual std::string test() = 0;
};

struct Y : X
{
    virtual int test() { return 42; } // ERROR! std::string and int are
                                      // unrelated, just as Q<A> and Q<B>
};

调用test()指针的客户端X会期望结果是 a string,但是“哎呀!”,该指针指向的对象是 type Y,返回类型Y::test()int。应该发生什么?运行时崩溃?

Y y;
X* p = &y;
std::string s = p->test(); // D'OH!

C++ 是一种静态类型语言,这意味着类型检查是在编译时执行的。在这种情况下,来自编译器的消息会告诉您派生类不遵守派生类的接口。

如果您想知道“无效协变返回类型”是什么意思,尤其是“协变”这个词,这很容易解释。

假设您有一个基类,该基类B带有一个foo()返回的虚函数X*

struct B
{
    virtual X* foo();
};

并假设您有一个D派生自该类的类,它通过返回一个B来覆盖,其中派生自的类是:foo()Y*YX

struct D : B
{
    virtual Y* foo();
};

这是一个问题吗?好吧,正确的答案来自回答这个稍微好一点的问题:“对于期望返回an的客户端调用来说,这会是一个问题foo()X*吗? ”

这个问题的答案显然是“否”,因为Y它是 的派生类X,所以你可以返回一个指向Y而不是指向的指针X

D d;
B* b = &d;
X* p = b->foo(); // Returns an Y*, but that's OK, because a pointer to Y can be
                 // assigned to a pointer to X

这是协变返回类型的示例。在您的示例中, of的返回类型与 .C::test() 的返回类型不协变B::test()

于 2013-06-22T19:20:30.087 回答
1

具有签名的函数B::test(void)返回一个类型为 的对象Q<A>,而C::test(void)(这是相同的签名,因此您正在覆盖该函数)返回Q<B>。我认为这是不可能的。

据我所知,不可能通过返回类型重载函数,并且父函数的覆盖需要坚持相同的返回类型。

来自标准§10.3/7

覆盖函数的返回类型应与被覆盖函数的返回类型相同或与函数的类协变。如果函数 D::f 覆盖函数 B::f,则函数的返回类型如果满足以下条件,则它们是协变的:

  • 都是指向类的指针,都是对类的左值引用,或者都是对类的右值引用112
  • B::f 的返回类型中的类与 D::f 的返回类型中的类是同一类,或者是 D:: 的返回类型中的类的明确且可访问的直接或间接基类F
  • 两个指针或引用都具有相同的 cv 限定,并且 D::f 的返回类型中的类类型具有与 B::f 的返回类型中的类类型相同或更少的 cv 限定。
于 2013-06-22T19:19:32.473 回答
0

你不能这样做。虚函数的覆盖不能改变函数的原型,除非是非常特殊的情况,例如协变返回类型。

如果您要在虚拟覆盖中返回虚拟基中返回的类型的子类,则协变返回将是有效的。但是您的Q<A>Q<B>与继承无关。这B是一个子类的事实A在这里没有任何区别。

于 2013-06-22T19:21:03.853 回答