请考虑以下设置。
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
我想知道的是,当使用A
或B
作为堆栈上分配的对象时,我必须支付相同的费用,如下面的代码所示。
A a;
a.F();
在堆栈上分配时,A
和B
是否有vtable?是否可以内联F
的电话?
似乎 对我来说,编译器可以为继承层次结构中的类创建两个内存布局 - 一个用于堆栈,另一个用于堆。这是C ++编译器将要/可能做的事情吗?或者它有不可能的理论或实际原因?
编辑:
我看到了一条评论(看起来已被删除)实际上提出了一个好点。您总是可以执行以下操作,然后在堆栈上分配A a
可能不是我想要的重点......
A a;
A* p = &a;
p->F(); //likely won't be inlined (correct me if I'm wrong)
也许更好的方法来表达它是“对于在堆栈上分配的对象的行为是否不同并且被用作'常规值类型'?”如果你知道我的意思,请用这个术语帮助我,但是有更好的方法来实现它!
我试图了解的一点是,你可以在编译时将基类的定义“压扁”到你在堆栈上分配实例的派生类。
答案 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将跳转到该功能。使用内联代码,函数调用的站点将替换为实际代码(类似于宏)。
如果在堆栈上分配了继承层次结构中包含的对象,则编译器仍需要能够确定它可以调用哪些函数,尤其是在存在虚函数和非虚函数的情况下。