我正在尝试覆盖基类函数。派生类型和基类型都返回指针,所以根据我在google上阅读的一些帖子和这里的stackoverflow,它应该是协变的。
但是在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
中编译并正常工作。
答案 0 :(得分:5)
MSVC是对的; void*
与T*
不一致。引用标准(10.3 [class.virtual],第7节):
覆盖函数的返回类型应与被覆盖的返回类型相同 与函数类的函数或协变。如果函数
D::f
覆盖函数B::f
,则 如果函数的返回类型满足以下条件,则它们是协变的:- 两者都是指向类的指针,都是对类的左值引用,或者两者都是对它们的右值引用 类
- 返回类型
中类的明确且可访问的直接或间接基类B::f
中的类与返回类型D::f
中的类相同,或者是 返回类型D::f
- 指针或引用在
D::f
的返回类型中具有相同的cv-qualification和类类型 与B::f
的返回类型中的类类型具有相同的cv资格或更少的cv资格。
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
,以便从V*
返回的D::f
始终可以转换为U*
。
现在,在这种情况下允许U
为void
合理,因为任何指向对象类型的指针都可以转换为void*
,甚至虽然void
不是任何基类。但该标准并不要求允许它。
该标准还说(1.4 [intro.compliance],第8段),
符合要求的实现可能具有扩展(包括其他库函数),只要它们可以 不改变任何格式良好的程序的行为。诊断程序需要实现 根据本国际标准使用不正确的扩展。但是,这样做了 他们可以编译和执行这样的程序。
所以g ++不是错误的。允许U
为void
是一个扩展名,它不会改变任何格式正确的程序的行为,并且g ++ 会发出警告你试着编译这段代码。
答案 1 :(得分:1)
在这种情况下,gcc是错误的,VS正确拒绝代码。该标准在10.3 / 7中对此进行了处理,其中定义了协变的含义。该定义要求返回类型都是指针或对类的引用。由于void
不是类,因此您提供的代码不会显示协方差。
Gcc错误地接受了这段代码。
答案 2 :(得分:0)
MSVC2013是对的。
何时致电
ptr->data();
编译器不知道使用哪种方法,可能是PSBaseObject::data()
或PSObject<data>::data();
所以你需要修改你的设计。