重写虚函数协变返回类型(两个指针)

overriding virtual function covariant return types (both pointers)

本文关键字:两个 指针 返回类型 函数 重写      更新时间:2023-10-16

我正在尝试覆盖基类函数。派生类型和基类型都返回指针,因此根据我在谷歌和堆栈溢出上阅读的一些帖子,它应该是协变的。

但是,对于以下类MSVC2013:

class PSBaseObject
{
public:
PSBaseObject() {}
virtual ~PSBaseObject() {}
virtual void* data() { return this; }
virtual const void* data() const { return this; }
};
template<typename T>
class PSObject : public PSBaseObject
{
private:
T* data_;
public:
PSObject(T* object) : data_(object) {}
~PSObject() { delete data_; }
T* data() { return data_; }
const T* data() const { return data_; }
};

我收到一个错误:

'PSObject<data>::data': overriding virtual function return type differs and is not covariant from 'PSBaseObject::data'

其中数据定义和使用方式如下:

typedef struct
{
void* hFileMap;
void* pData;
std::size_t size; 
} data;
data* info = new data();
auto ptr = new PSObject<data>(info);

为什么它不是协变的?

知道我在MSVC2013做错了什么吗?代码在g++ 4.8.1中编译并工作正常。

MSVC 是对的;void*T*不协变。引用标准(10.3 [class.virtual],第 7 节):

重写函数的返回类型

应与重写函数的返回类型相同 函数或与函数类的协变。如果函数D::f覆盖函数B::f, 如果函数的返回类型满足以下条件,则它们是协变的:

— 两者都是指向类的指针,都是对类的左值引用,或者都是对 类

— 返回类型B::f

中的类与返回类型D::f中的类相同,或者是一个 返回类型中类的明确且可访问的直接或间接基类D::f

— 指针或引用具有相同的 CV 资格和返回类型中的类类型D::f与返回类型中的类类型具有相同或更少的简历资格B::f

void不是T(=data) 的基类,因此返回类型不是协变的。


那么为什么会有这个规则呢?好吧,这个想法是,如果你有

struct B {
virtual U* f();
};
struct D : B {
virtual V* f();
};
B* b1 = new B();
B* b2 = new D();
U* u1 = b1->f();
U* u2 = b2->f();

b1->f()将调用B::f,返回U*。但是b2->f()要调用D::f,返回V*V必须从U派生,以便从D::f返回的V*始终可以转换为U*

现在,在这种情况下允许voidU合理的,因为任何指向对象类型的指针都可以转换为void*,即使void不是任何东西的基类。但标准并不要求允许。

该标准还规定(1.4 [介绍合规],第 8 段),

符合要求的实现可能有扩展(包括额外的库函数),前提是它们有 不更改任何格式良好的程序的行为。需要实现来诊断以下程序: 使用根据本国际标准格式错误的扩展。然而,在这样做之后, 他们可以编译和执行此类程序。

所以g++没有。允许Uvoid是一个扩展,它不会改变任何格式良好的程序的行为,并且 g++会在您尝试编译此代码时发出警告。

在这种情况下,gcc 是错误的,而 VS 拒绝代码是正确的。该标准在 10.3/7 中处理了这一点,其中定义了协变的含义。该定义要求两个返回类型都是对类的指针或引用。由于void不是类,因此您提供的代码不显示协方差

GCC 错误地接受了代码。

MSVC2013是对的。

你什么时候打电话

ptr->data();

编译器将不知道使用什么方法,可能是PSBaseObject::data()PSObject<data>::data();

所以你需要修复你的设计。