概述:在运行时将一个基类对象/指针的虚表指针(vptr)换成子类实例的虚表指针,从而在不改动对象内存布局的前提下,让一个基类对象“临时/动态地”表现出子类的多态行为。

原理

在 C++ 中,只要有虚函数,对象首地址处就会存一个指向**虚函数表(vtable)**的指针(vptr)。多态的本质就是:通过调用对象的虚表指针找到正确的虚函数入口

所以如果能直接改写基类对象自身的 vptr,让它指向子类的虚表,那么这个对象在后续调用虚函数时,就会走进子类的实现——这就是“动态声明子类”的核心思路。

关键步骤

  1. static_assert 保证基类与子类不新增成员变量sizeof 相等),否则改写 vptr 后对象内存会被越界使用。
  2. 通过 LPVOID* 取出基类对象首地址处的 vptr 与临时子类实例的 vptr
  3. 把基类对象的 vptr 覆盖为子类的 vptr
C++cpp
#include <iostream>
#include <cstdlib>
using namespace std;
class BaseClass 
{
public:
    BaseClass()
    {
        cout<<"BaseClass Construct"<<endl;
    }
    virtual ~BaseClass()
    {
        cout<<"BaseClass Destruct"<<endl;
    }
    virtual void  TestFunction()
    {
        cout<<"BaseClass TestFunction"<<endl;
    }
protected:
    int Test;
};
 
class SonClass : public BaseClass
{
public:
    SonClass()
    {
        cout<<"SonClass Construct"<<endl;
    }
    virtual ~SonClass()
    {
        cout<<"SonClass Destruct"<<endl;
    }
    virtual void  TestFunction()
    {
        cout<<"SonClass TestFunction"<<endl;
    }
};
 
typedef void           *LPVOID;
template<class _baseClass, class _derivedClass>
bool DoDynamicSubclassing(_baseClass *pBaseObj)
{
    if(pBaseObj)
    {
        static_assert(sizeof(_baseClass) == sizeof(_derivedClass), "DynamicSubclassing to _derivedClass must not have its own members");
        _derivedClass _derivedClassinstance;
        LPVOID *pBaseVirtPtr = (LPVOID*)pBaseObj, *pDerivedVirtPtr = (LPVOID*)&_derivedClassinstance;
        //assign the virtual ptr of the derived class
        *pBaseVirtPtr = *pDerivedVirtPtr;
        return true;
    }
    return false;
}
 
int main()
{
    BaseClass *Test = new BaseClass;
    cout << *(int*)Test << endl;
    DoDynamicSubclassing<BaseClass, SonClass>(Test);
    Test->TestFunction();
    cout << *(int*)Test << endl;
    
    cout << " ————————- " <<endl;
    BaseClass aaaa;
    cout << *(int*)&aaaa << endl;
    DoDynamicSubclassing<BaseClass, SonClass>(&aaaa);
    aaaa.TestFunction();
    cout << *(int*)&aaaa << endl;
    return 0;
}

输出与分析

运行后会看到:

text
BaseClass Construct
<基类 vptr 地址>
SonClass TestFunction     ← 明明是 BaseClass*,却调用了子类实现
<子类 vptr 地址>
BaseClass Construct
BaseClass Destruct        ← 栈上对象销毁时仍走基类析构
<子类 vptr 地址>
SonClass TestFunction
...

Test->TestFunction() 在改写 vptr 后输出 SonClass TestFunction,说明多态已生效。

注意事项 / 风险

  • 危险操作:直接篡改 vptr 属于未定义行为(UB),不同编译器、不同对象布局下行为可能不同,生产代码中应避免。
  • 只能用于无新增成员的子类,否则越过 static_assert 会导致内存越界。
  • 析构函数不会被正确替换:栈上对象销毁时仍调用基类析构,堆对象若 delete 基类指针(基类析构为 virtual)则仍走基类析构,不会执行子类析构逻辑。
  • 更安全的替代方案:工厂模式、虚函数、std::variant、策略模式等,能够在不破坏封装的前提下实现运行时多态。

实际业务中几乎不会直接采用这种 hack 方式,更多是作为一种理解 vptr / vtable 布局的学习示例。