【发布时间】:2017-10-21 01:28:15
【问题描述】:
我不确定这是 visual-c++ 编译器中的错误还是未定义的行为。
设置
struct DummyBase { virtual ~DummyBase() = default; };
struct DummyDerived : virtual public DummyBase {};
只是一个类和一个使用虚拟继承的派生类
DummyDerived derived;
DummyBase* base = &derived;
std::cout << "Derived : " << &derived << std::endl;
std::cout << "Base : " << base << std::endl;
将DummyDerived* 转换为DummyBase* 时,指针偏移。这似乎是由虚拟继承引起的:
Derived : 00000000002CF838
Base : 00000000002CF840
即使指针值不同,比较也会返回 true:
std::cout << "IsSame : " << (base == &derived) << std::endl << std::endl;
输出:
IsSame : 1
到目前为止一切顺利。
问题
问题出现在以下设置中:
struct IBaseReturner
{
virtual DummyBase* Get() = 0;
};
struct IDerivedReturner : public virtual IBaseReturner
{
virtual DummyDerived* Get() = 0;
};
struct BaseReturner : public virtual IBaseReturner
{
};
struct DerivedReturner : public BaseReturner, public virtual IDerivedReturner
{
DummyDerived* Ptr;
virtual DummyDerived* Get() override { return Ptr; }
};
这里我们有类的接口和实现,其方法返回DummyBase 或DummyDerived,通过协变返回类型覆盖。再次使用虚拟继承。
// Setup
DerivedReturner returner;
returner.Ptr = &derived;
IBaseReturner* baseReturner = &returner;
现在将DummyDerived*从DerivedReturner和DummyBase*从同一个返回者转换为IBaseReturner:
DummyDerived* derivedOriginal = returner.Get();
DummyBase* baseFromInterface = baseReturner->Get();
像上面一样比较:
std::cout << "Derived Original : " << derivedOriginal << std::endl;
std::cout << "Base from Interface : " << baseFromInterface << std::endl;
输出
Derived Original : 00000000002CF838
Base from Interface : 00000000002CF838
与上面不同,指针具有相同的值。 现在比较它们:
std::cout << "IsSame : " << (baseFromInterface == derivedOriginal) << std::endl;
输出:
IsSame : 0
比较返回false,即使地址相同。这是意料之中的,因为指向DummyBase 的指针应该具有不同的值。
同样在尝试进行 dynamic_cast 时:
std::cout << dynamic_cast<DummyDerived*>(baseFromInterface);
并且抛出异常:
unknown file: error: C++ exception with description "Access violation - no RTTI data!" thrown in the test body.
显然是因为指针没有正确偏移。
结论
似乎在调用IBaseReturner::Get 时,visual-c++ 编译器未能执行必要的指针运算以将DummyDerived* 转换为DummyBase*。这发生在 vs2013 和 vs2015 中(没有尝试任何其他版本)。使用 gcc 编译时也可以正常工作。
问题
虽然设置可能有点复杂,但问题却相当简单:
这是一个 msvc 错误还是我导致了未定义的行为?
添加了一个在线示例:http://rextester.com/KHZXGQ27304
【问题讨论】:
-
我很惊讶
IBaseReturner* baseReturner = &returner;编译;在returner中有两个不同的IBaseReturner实例。我可以想象 C++ 标准会采用任何一种方式(一条路径更短),但我预计该行会出现错误。 -
struct DerivedReturner : public BaseReturner, public virtual DerivedReturner那不编译,也不可能。你有一个从它自己派生的类。我假设你的意思是从IDerivedReturner -
@Yakk
IBaseReturner是一个虚拟基类;事实上,DerivedReturner中只有一个IBaseReturner实例,这使得演员阵容明确。 -
@igor 它只有一条路径是虚拟的。这不是实际的代码吗?需要minimal reproducible example,而不是伪代码。投票结束,直到 OP 发布一个最小且准确的示例,其中包含一个证明代码准确的链接。我怀疑实际的代码可能涉及不同的恶作剧,但很难在我的手机上重新输入,结果我也必须修复它。
-
@Yakk 你是什么意思,只有一条路径是虚拟的?没有一个地方可以非虚拟继承
IBaseReturner。 Here's an MCVE 。对我来说,这看起来像是一个 MSVC 错误。
标签: c++ visual-c++ visual-studio-2013 virtual-inheritance covariant-return-types