Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are inheritance costs relative to whether objects are allocated on the stack or heap?

Consider the following setup.

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 */ }
};

This allows me to write a function like the following.

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

In this situation, I am paying some costs for the inheritance and polymorphism, namely having a vtable and that calls to F can't be inlined when used like follows (correct me if I'm wrong).

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

What I want to know is if I have to pay these same costs when using A or B as objects allocated on the stack, like in the following code.

A a;
a.F();

Do A and B have vtables when allocated on the stack? Can the call to F be inlined?

It seems to me that a compiler could feasibly create two memory layouts for classes in an inheritance hierarchy - one for the stack and one for the heap. Is this what a C++ compiler will/may do? Or is there a theoretical or practical reason it can't?


Edit:

I saw a comment (that looks like it was deleted) that actually raised a good point. You could always do the following, and then that A a was allocated on the stack might not be the salient point I'm trying to get at...

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

Maybe a better way to phrase it would be "Is the behavior different for an object that is allocated on the stack and is used as a 'regular value type'?" Please help me out here with the terminology if you know what I mean but have a better way of putting it!

The point I'm trying to get at is that you could feasibly, at compile time, "flatten" the definition of the base class into the derived class you are allocating an instance of on the stack.

like image 310
Timothy Shields Avatar asked May 17 '13 22:05

Timothy Shields


2 Answers

I think your question really has to do with whether a compiler has static knowledge of an object and can elide the vtable lookup (you mentioned this in your edit), rather than whether there is a distinction on where the object lives - stack or heap. Yes, many compilers can elide the virtual dispatch in that case.

like image 194
Scott Jones Avatar answered Nov 10 '22 00:11

Scott Jones


The edit to your question asks whether you can flatten the definition of the base class, A, into the derived class, B. If the compiler can tell, at compile time, that an object will only ever contain an instance of B then it can eliminate the vtable lookup at runtime and call B.F(); for that particular call.

For example, the compiler will probably eliminate the vtable lookup at runtime below and call the derived function:

B b;
b.F();

In the code below, the compiler will not be able to eliminate the runtime lookup in doSomething, but it probably can eliminate the lookup in 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 );

Note it does not matter whether object is allocated on the stack or the heap. What matters is that the compiler is able to determine the type. Each class will still have a vtable, it just might not always be needed for each method call.

You mention code inlining, this is not related to how the object is allocated. When a normal function is called, variables will be pushed onto the stack along with a return address. The CPU will then jump to the function. With inline code, the site of the function call is replaced with the actual code (similar to a macro).

If an object contained in an inheritance hierarchy is allocated on the stack, the compiler still needs to be able to determine what functions it can call, especially if there are virtual and non-virtual functions.

like image 27
Steve Avatar answered Nov 09 '22 22:11

Steve