虚拟继承中来自基数的虚拟调用

Virtual call from base in virtual inheritance

本文关键字:虚拟 调用 继承      更新时间:2023-10-16

使用虚拟继承时进行虚拟调用时遇到问题。

下面是示例可编译代码,它演示了在不使用虚拟继承时工作的代码,以及在使用虚拟继承时在运行时失败的代码。

基类以下是两种情况的基础小牛:

#include <iostream>
class Base
{
public:
Base() { }
virtual ~Base() { }
// we need to make this bad call a good one!
virtual void bad_call(void* ptr)
{
Base* pThis = static_cast<Base*>(ptr);
pThis->f();
}
protected:
virtual void f() { std::cout << x << std::endl; }
int x = 0;
};
class Midle1 :
virtual public Base
{
public:
Midle1() { }
~Midle1() override { }
};
class Midle2 :
virtual public Base
{
public:
Midle2() { }
~Midle2() override { }
};

案例 1 好下面是一个不使用虚拟继承(只是普通继承(的情况,其中bad_callgood_call虚函数都工作:

class GoodDerived :
public Base
{
public:
GoodDerived()
{
}
~GoodDerived() override
{
}
void good_call(void* ptr)
{
GoodDerived* pThis = static_cast<GoodDerived*>(ptr);
pThis->f();
}
void f() override
{
++x;
std::cout << x << std::endl;
}
};
int main()
{
GoodDerived good_derived;
good_derived.good_call(&good_derived);  // OK, will print 1
good_derived.bad_call(&good_derived);   // OK, will print 2
std::cin.get();
return 0;
}

案例 2 坏这是一个使用虚拟继承的情况,good_call函数将成功,但bad_call函数将失败并显示"访问冲突读取位置">

class BadDerived :
public Midle1,
public Midle2
{
public:
BadDerived() { }
~BadDerived() override { }
void good_call(void* ptr)
{
BadDerived* pThis = static_cast<BadDerived*>(ptr);
pThis->f();
}
void f() override
{
++x;
std::cout << x << std::endl;
}
};
int main()
{
BadDerived bad_derived;
bad_derived.good_call(&bad_derived); // OK, will print 1
bad_derived.bad_call(&bad_derived); // ERROR: causes access violation
std::cin.get();
return 0;
}

问题第二种情况是一个简单的代码,演示了我现在在项目中遇到的问题,我需要有关如何解决此问题的帮助,为什么虚拟继承会引起麻烦?

为什么第一种情况工作正常,而第二种情况则不然?

基本问题是您将指针强制转换为void *,然后将其转换为其他指针类型。 这通常不起作用 - 在将指针投射到void *之后,您可以用它做的唯一有用的事情就是将其投射回完全相同的指针类型。 如果要强制转换为任何其他指针类型(可靠地(,则需要首先强制转换回相同的原始类型。

我需要有关如何解决此问题的帮助

不要在这里使用void *-void *是一个永远不应该在C++中使用的 C 解决方案。 更改虚拟bad_call方法,将Base *作为参数而不是void *。 然后一切都"正常工作",您根本不需要任何static_cast。 如果你需要在Dervied类中覆盖bad_call,它还需要一个Base *参数,所以你需要在那里使用dynamic_cast<Derived *>(ptr)来取回原始Derived *,但这没什么大不了的——这正是dynamic_cast存在的目的。

拨打您的电话:

bad_derived.bad_call(static_cast<Base*>(&bad_derived));

您希望指向对象的 Base 部分,但在使用虚拟继承时,无法保证该部分的位置。

让我们一步一步地分解它。

  1. &bad_derived:指向BadDerived的指针,指针值指向类型为BadDervived的对象。
  2. bad_derived.bad_call(&bad_derived):隐式将&bad_derived转换为指向void*的指针,指针值指向类型为BadDervived的对象。
  3. Base* pThis = static_cast<Base*>(ptr);: 从void*Base*的演员。请注意,ptr具有指向类型为BadDervived的对象的指针值,但BadDerived不能与Base进行指针互转换,因此pThis具有类型Base*但指针值指向类型为BadDervived的对象。
  4. pThis->f();:使用类型为Base的 glvalue(此处为取消引用的指针(访问BadDerived对象的值,违反了严格别名规则。未定义的行为。

我想分享一个使这种设计成为可能的解决方案(在其他答案和评论的帮助下(。

所有代码保持不变,除了将模板化的静态 mehtod 添加到基类中,这将推断出 void 到正确的类型:

这是带有帮助程序模板静态函数的修改基类: 还添加了关于CALLBACK的评论。

class Base
{
public:
Base() { }
virtual ~Base() { }
// this is example CALLBACK from Window API but templated
// The callback is registered with Windows in RegisterClassEx btw.
template<typename DERIVED_CLASS>
static void make_call(void* ptr)
{
DERIVED_CLASS* pThis = static_cast<DERIVED_CLASS*>(ptr);
pThis->bad_call(static_cast<Base*>(pThis));
}
// we need to make this bad call a good one!
virtual void bad_call(void* ptr)
{
Base* pThis = static_cast<Base*>(ptr);
pThis->f();
}
protected:
virtual void f() { std::cout << x << std::endl; }
int x = 0;
};

以下是我们如何调用bad_call有问题的函数:

int main()
{
BadDerived bad_derived;
bad_derived.good_call(&bad_derived); // OK, will print 1
// HACA!!!
bad_derived.make_call<BadDerived>(&bad_derived);    // OK will print 2
std::cin.get();
return 0;
}

这就是为什么我非常喜欢C++,一切皆有可能......