虚拟继承中来自基数的虚拟调用
Virtual call from base in virtual inheritance
使用虚拟继承时进行虚拟调用时遇到问题。
下面是示例可编译代码,它演示了在不使用虚拟继承时工作的代码,以及在使用虚拟继承时在运行时失败的代码。
基类以下是两种情况的基础小牛:
#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_call
和good_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 部分,但在使用虚拟继承时,无法保证该部分的位置。
让我们一步一步地分解它。
&bad_derived
:指向BadDerived
的指针,指针值指向类型为BadDervived
的对象。bad_derived.bad_call(&bad_derived)
:隐式将&bad_derived
转换为指向void*
的指针,指针值指向类型为BadDervived
的对象。Base* pThis = static_cast<Base*>(ptr);
: 从void*
到Base*
的演员。请注意,ptr
具有指向类型为BadDervived
的对象的指针值,但BadDerived
不能与Base
进行指针互转换,因此pThis
具有类型Base*
但指针值指向类型为BadDervived
的对象。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++,一切皆有可能......
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 当覆盖存在时调用基本虚拟"binded to object"函数
- C++ DLL 类导出、编译器"optimizes"虚拟调用
- 类型擦除的std::function与虚拟函数调用的开销
- 虚拟继承中来自基数的虚拟调用
- 通过在基类中虚拟调用派生类中的函数来访问派生类中的函数
- C++虚拟函数是正常调用还是虚拟调用
- 为什么C++虚拟调用并不比非虚拟调用慢多少
- 避免虚拟调用循环的模式
- C++11 标准::功能比虚拟调用慢
- 构造函数中的虚拟调用
- 为什么C++虚拟调用的成本取决于派生类的数量
- 为什么从构造函数对纯虚函数的虚拟调用是 UB,而标准允许调用非纯虚函数
- 优化后的虚拟调用成本
- 对于C++基类来说,存储"this"的值以供构造后的未来虚拟调用安全吗?
- 构造/销毁期间的虚拟调用
- 用FlasCC编译的c++程序使用-O4 (LTO)挂起虚拟调用
- 基构造函数中的 C++/CX 委托失败,并出现纯虚拟调用断言
- 如何将虚拟调用从未模板化的父级干净地分派到模板化的子级
- std::绑定和对基类方法的非虚拟调用