c++虚函数表问题

class Base
{
public:

    virtual ~Base()
    {
    }

    virtual void func()
    {
        cout << "Base func()\n";
    }
};

class BasePlus : public Base
{
public:

    virtual ~BasePlus()
    {
    }

    virtual void func()
    {
        cout << "BasePlus func()\n";
    }
};

int main()
{
    BasePlus bp;
    Base b = bp;
    b.func();  // Base func()

    return 0;
}

一个类存在虚函数,那么对象创建的时候就会多一个虚函数指针 vptr,指向虚函数表。

上述代码,派生类对象直接赋值给基类对象,那么 BasePlus.vptr 不是也赋值给了 Base.vptr 了么,为什么调用 b.func() 却没有输出 BasePlus func() 呢?

当时在学多态的时候,一直没仔细思考过这个问题。现在想想,我的猜测如下:

应该是 vptr 压根没赋值过去。Base b = bp 执行的是 Base 的默认 = 运算符操作,进行的是 memberwise copy。

还请各位大兄弟指点迷津。

阅读 1.7k
2 个回答

Base b = bp; 执行的不是 Base 的 operator= ,而是拷贝构造函数。构造的结果一定是一个 Base,而不是 BasePlus

默认的 operator= 的逻辑是对每一个子对象调用 operator=默认的 oprator= 的参数const Base &。vptr 是实现细节,不属于子对象。赋值之后,被赋值的对象依然是 Base 的对象,不会变成 BasePlus 的对象,对 vptr 的操作(如果有)必须保证这一点。

这个问题挺有意思,试验了一下确实如此。来反推一下过程。

// sizeof(Base) = 8
// sizeof(BasePlus) = 8

BasePlus bp;
Base b;
memcpy(&b, &bp, sizeof(b));
b.func();  // Base func()

感觉上,b.func() 应该根据虚函数表调用 BasePlus::func。但可能因为这里用了直接调用,从而使“选择”这个过程在编译期就决定了。

用 godblot.org 看了一下,确实如此。clang 在这里直接生成了 call Base::func()

进一步,这个反直觉的事情我猜应该是个 UB(未定义行为),所以编译器怎么做都对,不讲道理的。


虚函数的通常用法应该如下所示:

BasePlus bp;
Base* b = new Base();
memcpy(b, &bp, sizeof(b));
b->func();  // BasePlus func()
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题