我不确定这是 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;
现在从DerivedReturner
返回DummyDerived*
,从同一返回器转换到IBaseReturner
DummyBase*
:
DummyDerived* derivedOriginal = returner.Get();
DummyBase* baseFromInterface = baseReturner->Get();
比较 就像 obove 一样:
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
您的代码没有未定义的行为(GCC 和 Clang 都可以很好地编译和执行它,演示:https://gcc.godbolt.org/z/q91vbhjTh),并且由于 MSVC 中的错误,其运行失败,该错误仍然存在于 Visual Studio 2019 和 2022 中。
报告的 MSVC 错误:https://developercommunity.visualstudio.com/t/Invalid-code-generated-for-virtual-inher/1598214
从他们的回应来看:
这与编译器中涉及虚拟继承和协变返回类型时虚拟函数表布局中的一些问题有关。 如果被阻止,一种可能的解决方法是交换基类的顺序:
struct DerivedReturner : public virtual IDerivedReturner, public BaseReturner