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

Cat is not covariant with Animal when overriding virtual method

当我学习 C++ 中的方法覆盖规则时,我一直在阅读协变类型,它基本上似乎是子 class 或派生 class(至少在 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++ 才会阻止您通过复制 returning 协变类型,请参阅 Fire Lancer 关于可能原因的出色回答。还有一个类似这个的问题,评论里有有趣的讨论,是不是因为调用者不知道case中的return类型要分配多少space的虚拟通话。

overriding virtual function return type differs and is not covariant

C++ 中的协变 return 类型意味着覆盖 return 类型必须是引用或指针。

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

例如可以说这是允许的,我有两种类型 AB 我想使用 XY.[=29= 的协变 returns ]

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();

但是通过在实际上 return 是 BY 实例上调用 get_a,它必须将 B 转换为 [=13= ...现在是 "outside" 对象)。

并且在一般情况下,程序员或编译器都无法判断这可能发生在 A a = x->get_a() 行,因为 X 可以由任何东西得出(甚至可能在编译器潜在知识之外,例如在单独的 DLL 中!)。

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

class X
{
public:
    A get_a();
};
class Y : public X
{
public:
    B get_a(); // Not an override!
}
X *x = new Y();
A a = x->get_a(); // still called X::get_a, no slice ever!


Y *y = new Y();
A a = y->get_a(); // calls Y::get_a, which slices, but the compiler and programmer can tell that from static typing.