协变返回类型和类型转换

9

s->duplicate() 返回一个类型为 Box* 的对象,但我在使用 Box* 初始化时出现了错误。看起来它被转换回了 Shape*。如果它被转换回基类指针,那么有什么意义使用协变返回类型呢?

struct Shape
{
    virtual Shape* duplicate()
    {
        return new Shape;
    }
};

struct Box : Shape
{
    virtual Box* duplicate()
    {
        return new Box;
    }
};

int main()
{
    Shape* s = new Box;
    Box*   b = s->duplicate();
}

错误:

main.cpp:22:12: error: cannot initialize a variable of type 'Box *' with an rvalue of type 'Shape *'
    Box*   b = s->duplicate();
           ^   ~~~~~~~~~~~~~~
1 error generated.
2个回答

11
虽然在运行时(通过虚函数调用)调用了Box::duplicate,并且Box::duplicate的重载确实覆盖了Shape::duplicate(协变),并且Box::duplicate确实返回一个Box*,但由于您是通过Shape*指针调用duplicate(),所以仍将得到一个Shape*指针,因为Shape*Shape::duplicate()的返回类型,编译器只看到您调用了Shape::duplicate,而没有看到调用的是Box::duplicate
C ++无法动态选择类型,因此这是它所能做的最好的事情。在从Box::duplicate返回时,您的Box*会自动转换为Shape*。就像Barry所说的那样,“它仍然必须在编译时编译,在编译时我们只知道它返回一个Shape*”。
然后,要再次将其转换为Box*,您需要显式进行转换(使用static_castdynamic_cast),因为没有隐式的向下转换存在。
引用块中引用了C++11中的规则:覆盖函数的返回类型应该与被覆盖函数的返回类型相同或者与其协变。如果D::f的返回类型与B::f的返回类型不同,则D::f中返回的类类型必须是B::f中返回的类类型的公有派生类。在声明 D::f 的时候应该完整,或者是类类型D。当重写的函数作为最终的覆盖函数被调用时,它的结果会被转换成(静态选择的)被重写函数返回的类型(5.2.2)。在标准文本中,紧随其后是一个相关的示例。

他们真的不能想出一个更短的例子来说明这一部分吗? - Barry
@Barry:惊讶了吗? :P - Lightness Races in Orbit
@Kad:不确定你想表达什么。你是想问关于那行代码的问题吗?那是无效的,因为它是一种试图隐式从基类转换到派生类的转换,这是不允许的(我在答案中已经说过了)。请尝试使用Shape* s = new Box(); - Lightness Races in Orbit

5

重点不在于这样做:

Box*   b = s->duplicate();

显然这是不可能的,因为Shape::duplicate()返回一个Shape*。重点是如果你直接在Box上调用duplicate(),则应该接受一个Box*

Box* old = new Box;
Box* b = old->duplicate(); // OK! We know it's a Box

1
由于调度是在运行时执行的,为什么不从Box::duplicate()中获取返回类型? - template boy
2
@templateboy 因为它仍然需要在编译时编译,而在编译时我们只知道它返回一个 Shape* - Barry
解决方案并不是非常通用。如果我不知道duplicate()返回什么怎么办? - template boy
@templateboy 你知道它返回一个 Shape*。如果你知道 Box::duplicate() 返回一个 Box*,那么你可以利用这个信息吗?我不明白你的意思。 - Barry
我现在明白了。谢谢。 - template boy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接