覆盖虚拟方法时,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 添加)。
例如可以说这是允许的,我有两种类型 A
和 B
我想使用 X
和 Y
.[=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 是 B
的 Y
实例上调用 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.
当我学习 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 添加)。
例如可以说这是允许的,我有两种类型 A
和 B
我想使用 X
和 Y
.[=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 是 B
的 Y
实例上调用 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.