c++ - 重写虚拟方法时,Cat 与 Animal 不协变

标签 c++ inheritance overriding virtual

当我学习 C++ 中的方法重写规则时,我一直在阅读有关协变类型的内容,它基本上似乎是子类或派生类的一个奇特词(至少对于 C++ 而言)。我做了一些测试,发现有一个令人惊讶的异常(exception),我不明白。如下图所示:

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

最佳答案

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

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

例如可以说这是允许的,我有两种类型 AB我想用作 X 的协变返回和Y .

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_aY上实际上返回 B 的实例,它必须转换 BA隐式地,但这会“切掉”我的 B::c成员,这可能使其处于无效状态(特别是如果 A 有任何虚拟函数,并且这些函数需要 B::c 现在位于对象“外部”)。

在一般情况下,程序员或编译器都无法判断这可能发生在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.

关于c++ - 重写虚拟方法时,Cat 与 Animal 不协变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45884804/

相关文章:

c++ - 将 float 转换为 float/int 后返回不同的结果

c++ - 枚举和开关 block 哪里出错了

c++ - 模板层次结构中的可选虚函数取决于参数

java - 子类会继承私有(private)字段吗?

c# - 我需要访问组合框的非公共(public)成员(突出显示的项目)

c++ - 对象处理

c++ - 如何在win api中设置整个应用程序的字体?

c++ - 重新定义宏时会发生什么?

javascript - 有没有有效的方法在 javascript 中使用 `__proto__` 或 `setPrototypeOf()`?

c++ - 重写关键字在编译时抛出错误