多继承类指针转LPVOID再转回基类致_vptr错误的原因及解决
我在使用多继承的dlgderived类时遇到异常:将该类指针转为LPVOID存入various结构体,再转回imagecallback基类指针后调用虚函数会崩溃或死循环;但直接传递imagecallback指针给函数则运行正常。调试发现转回后的指针的_vptr指向dlgcommon类的DoThis(MSG)、DoThat(MSG)虚函数,而非imagecallback的Draw_This(ID2D1RenderTarget*)、Draw_That(ID2D1RenderTarget*)。尝试将基类虚函数改为非纯虚也未解决问题,请问该如何调整类型转换方法使其正常工作?
#include <windows.h> #include <d2d1.h> // some COM #includes might be missing here but hopefully the message is not lost in this example // various does not know any classes so LPVOID/void* is used typedef struct various { LPVOID drawobj = nullptr; int a = 0; int b = 0; } various; class dlgcommon { public: virtual void DoThis(MSG dlg_msg) = 0; virtual void DoThat(MSG dlg_msg) = 0; dlgcommon() {} ~dlgcommon() {} }; class imagecallback { public: virtual void Draw_This(ID2D1RenderTarget *arg_tgt) = 0; virtual void Draw_That(ID2D1RenderTarget *arg_tgt) = 0; imagecallback() {} ~imagecallback() {} }; class dlgderived : public dlgcommon, public imagecallback { public: // dlgcommon contract void DoThis(MSG dlg_msg) override {} void DoThat(MSG dlg_msg) override {} // imagecallback contract void Draw_This(ID2D1RenderTarget *arg_tgt) override {} void Draw_That(ID2D1RenderTarget *arg_tgt) override {} dlgderived() {} ~dlgderived() {} }; // This method works fine void Test_Via_Base_Ptr(imagecallback *basedraw) { // sample code ID2D1HwndRenderTarget *render_tgt = nullptr; basedraw->Draw_This(render_tgt); basedraw->Draw_That(render_tgt); } // This method does not work; the app crashes or gets stuck in a loop while in the debugger void Test_Via_Data(various data) { // sample code ID2D1HwndRenderTarget *render_tgt = nullptr; auto base_s = static_cast<imagecallback*>(data.drawobj); auto base_d = dynamic_cast<imagecallback*>(data.drawobj); // this method does not work base_s->Draw_This(render_tgt); base_s->Draw_That(render_tgt); // this method does not work base_d->Draw_This(render_tgt); base_d->Draw_That(render_tgt); } int main() { dlgderived mydlg; // This method works fine Test_Via_Base_Ptr(&mydlg); // This method does not work various mydata = { (LPVOID)&mydlg, 0, 0 }; Test_Via_Data(mydata); }
问题根源
多继承场景下,派生类对象的不同基类子对象在内存中拥有独立的虚表指针(_vptr),且地址可能存在偏移。直接将dlgderived*转为LPVOID时,保存的是派生类对象的起始地址(对应第一个基类dlgcommon的地址)。此时直接将LPVOID转回imagecallback*,编译器无法自动调整指针偏移,导致得到的指针指向dlgcommon的虚表,调用时执行错误的虚函数,引发崩溃或死循环。
修复方法
有两种可靠的调整方式:
方式1:存入LPVOID前先转为目标基类指针
在将指针存入various结构体时,先把dlgderived*转为imagecallback*,再转成LPVOID。这样取出时直接转回imagecallback*即可,因为保存的是imagecallback子对象的正确地址:
// 修改main函数中的存入逻辑 various mydata = { (LPVOID)static_cast<imagecallback*>(&mydlg), 0, 0 };
方式2:取出时先转回派生类指针再转基类指针
如果必须保存派生类对象的起始地址,取出时要先转回dlgderived*,再转为imagecallback*,让编译器自动处理指针偏移:
// 修改Test_Via_Data函数 void Test_Via_Data(various data) { ID2D1HwndRenderTarget *render_tgt = nullptr; // 先转回派生类指针,再转目标基类指针 auto derived_ptr = static_cast<dlgderived*>(data.drawobj); auto base_ptr = static_cast<imagecallback*>(derived_ptr); base_ptr->Draw_This(render_tgt); base_ptr->Draw_That(render_tgt); }
注意事项
- 禁止直接用
static_cast将LPVOID跨基类转换,这会跳过指针偏移计算; dynamic_cast无法直接对LPVOID生效,因为LPVOID丢失了类型信息,必须先转回完整的派生类指针(需开启RTTI)才能使用dynamic_cast做安全转换。
内容的提问来源于stack exchange,提问作者Sempai-Dami1

