是否在堆栈或堆上分配对象的继承成本是什么?

时间:2013-05-17 22:15:34

标签: c++ inheritance

请考虑以下设置。

class I
{
public:
    virtual void F() = 0;
};

class A : public I
{
public:
    void F() { /* some implementation */ }
};

class B : public I
{
public:
    void F() { /* some implementation */ }
};

这允许我编写如下函数。

std::shared_ptr<I> make_I(bool x)
{
    if (x) return std::make_shared<A>();
    else return std::make_shared<B>();
}

在这种情况下,我为继承和多态付出了一些代价,即拥有一个vtable,并且当使用如下时对F的调用无法内联(如果我错了,请纠正我)。

auto i = make_I(false);
i->F(); //can't be inlined

我想知道的是,当使用AB作为堆栈上分配的对象时,我必须支付相同的费用,如下面的代码所示。

A a;
a.F();

在堆栈上分配时,AB是否有vtable?是否可以内联F的电话?

似乎 对我来说,编译器可以为继承层次结构中的类创建两个内存布局 - 一个用于堆栈,另一个用于堆。这是C ++编译器将要/可能做的事情吗?或者它有不可能的理论或实际原因?


编辑:

我看到了一条评论(看起来已被删除)实际上提出了一个好点。您总是可以执行以下操作,然后在堆栈上分配A a可能不是我想要的重点......

A a;
A* p = &a;
p->F(); //likely won't be inlined (correct me if I'm wrong)

也许更好的方法来表达它是“对于在堆栈上分配的对象的行为是否不同并且被用作'常规值类型'?”如果你知道我的意思,请用这个术语帮助我,但是有更好的方法来实现它!

我试图了解的一点是,你可以在编译时将基类的定义“压扁”到你在堆栈上分配实例的派生类。

2 个答案:

答案 0 :(得分:7)

我认为你的问题实际上与编译器是否具有对象的静态知识有关,并且可以忽略vtable查找(你在编辑中提到过这一点),而不是是否区分对象所在的位置 - 堆栈或堆。是的,在这种情况下,许多编译器可以忽略虚拟调度。

答案 1 :(得分:1)

您的问题的编辑询问您是否可以将基类A的定义压缩到派生类B中。如果编译器在编译时可以告诉对象只包含B的实例,那么它可以在运行时消除vtable查找并为该特定调用调用B.F();

例如,编译器可能在运行时消除vtable查找并调用派生函数:

B b;
b.F();

在下面的代码中,编译器将无法消除doSomething中的运行时查找,但可能可以消除b.F()中的查找

void doSomething( A* object ) {
    object->F();   // will involve a vtable lookup
}

B b;
b.F();    // probably won't need a vtable lookup
doSomething( &b );

请注意,对象是在堆栈还是堆上分配无关紧要。重要的是编译器能够确定类型。每个类仍然有一个vtable,每个方法调用可能并不总是需要它。

您提到代码内联,这与对象的分配方式无关。调用普通函数时,变量将与返回地址一起压入堆栈。然后CPU将跳转到该功能。使用内联代码,函数调用的站点将替换为实际代码(类似于宏)。

如果在堆栈上分配了继承层次结构中包含的对象,则编译器仍需要能够确定它可以调用哪些函数,尤其是在存在虚函数和非虚函数的情况下。