当覆盖虚拟方法时,Cat与Animal不协变

时间:2017-08-25 15:28:21

标签: c++ inheritance override virtual

在我学习C ++中覆盖规则的方法时,我一直在阅读协变类型,这对于子类或派生类来说基本上似乎是一个奇特的词(至少在尊重方面)到C ++)。我做了一些测试,发现有一个令人惊讶的例外,我不明白。如下所示:

struct Animal {};
struct Cat : Animal{};

Cat gCat;

Animal* getAnimalPointer() { return &gCat; }  // Returning covariant type by pointer, OK
Animal& getAnimalReference() { return gCat; } // Returning covariant type reference, OK
Animal getAnimalCopy() { return gCat; }       // Returning covariant type by copy, OK
// All good

// Now testing virtual method overriding by using C++ feature of 
// allowing overriding virtual methods using covariant return types
struct AnimalShelter
{
    virtual Animal* getAnimalPointer() {};
    virtual Animal& getAnimalReference() {};
    virtual Animal getAnimalCopy() {}
};
struct CatShelter : AnimalShelter
{
    Cat* getAnimalPointer() override;       // Returning covariant type by pointer, OK
    Cat& getAnimalReference() override;     // Returning covariant type by reference, OK
    Cat getAnimalCopy() override;           // Returning covariant type by copy, fail
    /* Visual Studio error: return type is not identical to nor covariant with 
    return type "Animal" of overriden virtual function CatShelter::getAnimalCopy*/
};

编辑:仅在虚拟情况下结果C ++会阻止您通过副本返回协变类型,请参阅Fire Lancer关于可能原因的优秀答案。还有另一个与此类似的问题,在评论中有一个有趣的讨论,其中的原因是调用者是否知道在虚拟调用的情况下为返回类型分配了多少空间。

overriding virtual function return type differs and is not covariant

1 个答案:

答案 0 :(得分:2)

C ++中的协变返回类型意味着重写返回类型必须是引用或指针。

值类型可以具有不同的大小,即使它们共享一个共同的基础。指针或引用通常相同,或者最多是字节移位(虚拟或多重继承)。 但转换值类型可能会导致切片,因为通过副本从较大的类型转换为较小的类型(即使基类型定义了复制构造函数或运算符,它们仍然经常切片,因为无处存储任何额外的字段派生类已添加)。

e.g。让我们说这是允许的,我有两种类型AB我想用作XY的协变回报。

struct A
{
    int x, y;
};
struct B : A
{
    int c;
};



class X
{
public:
    virtual A get_a();
};
class Y : public X
{
public:
    B get_a()override;
}

这里的问题是,当使用X的引用时,可能实际上是Y我可以这样做:

X *x = new Y();
A a = x->get_a();

但是,通过在实际返回get_a的{​​{1}}实例上调用Y,它必须隐式地将B转换为B,但这会"切片"关闭我的A会员,这可能会使其处于无效状态(特别是B::c有任何虚拟功能),以及预期A现在"外部"对象)。

在一般情况下,程序员或编译器都不会告诉这可能发生在B::c行,因为A a = x->get_a()可能被任何东西所衍生(甚至可能超出编译器的潜在知识,例如一个单独的DLL!)。

在非虚拟的情况下,编译器和程序员可以告诉它正在发生,所以尽管C ++确实允许切片,但至少知道它正在发生并且可能是编译器警告。

X