【问题标题】:Cat is not covariant with Animal when overriding virtual method [duplicate]覆盖虚拟方法时,猫与动物不协变[重复]
【发布时间】:2017-08-25 15:28:20
【问题描述】:

当我在学习 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 的出色回答,了解可能的原因。还有另一个与此类似的问题,在 cmets 中有一个有趣的讨论,关于这样做的原因是否是调用者不知道在虚拟调用的情况下为返回类型分配多少空间。

overriding virtual function return type differs and is not covariant

【问题讨论】:

  • 您使用Animal getAnimalCopy() { return Cat(); } 进行对象切片。
  • 是的,我认为那样会不那么混乱。
  • “但是从函数返回时似乎没问题” C++ 的问题在于,并非所有看起来都很好的东西实际上都很好;)
  • 似乎从函数、指针、引用和副本返回时,您可以返回协变类型,但是通过覆盖虚方法,您只能返回指针和引用。奇怪。

标签: c++ inheritance overriding virtual


【解决方案1】:

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

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

例如假设这是允许的,我有两种类型 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();

但是通过在实际返回BY 实例上调用get_a,它必须将B 隐式转换为A,但这会“切掉”我的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++ 在虚拟方法的情况下阻止它的原因是否是调用者不知道要为返回分配多少空间存在争论输入虚拟通话。
猜你喜欢
  • 2013-01-15
  • 2012-11-28
  • 2018-04-22
  • 2016-01-24
  • 2012-06-19
  • 2011-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多