If classes with virtual functions are implemented with vtables, how is a class with no virtual functions implemented?
Asked Answered
K

9

6

In particular, wouldn't there have to be some kind of function pointer in place anyway?

Kobe answered 19/9, 2008 at 12:3 Comment(1)
What's the context of the question? Is there a particular problem you are looking at or is this just general knowledge seeking?Ladd
T
12

Non virtual member functions are really just a syntactic sugar as they are almost like an ordinary function but with access checking and an implicit object parameter.

struct A 
{
  void foo ();
  void bar () const;
};

is basically the same as:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

The vtable is needed so that we call the right function for our specific object instance. For example, if we have:

struct A 
{
  virtual void foo ();
};

The implementation of 'foo' might approximate to something like:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}
Turgent answered 19/9, 2008 at 12:49 Comment(0)
S
15

I think that the phrase "classes with virtual functions are implemented with vtables" is misleading you.

The phrase makes it sound like classes with virtual functions are implemented "in way A" and classes without virtual functions are implemented "in way B".

In reality, classes with virtual functions, in addition to being implemented as classes are, they also have a vtable. Another way to see it is that "'vtables' implement the 'virtual function' part of a class".

More details on how they both work:

All classes (with virtual or non-virtual methods) are structs. The only difference between a struct and a class in C++ is that, by default, members are public in structs and private in classes. Because of that, I'll use the term class here to refer to both structs and classes. Remember, they are almost synonyms!

Data Members

Classes are (as are structs) just blocks of contiguous memory where each member is stored in sequence. Note that some times there will be gaps between members for CPU architectural reasons, so the block can be larger than the sum of its parts.

Methods

Methods or "member functions" are an illusion. In reality, there is no such thing as a "member function". A function is always just a sequence of machine code instructions stored somewhere in memory. To make a call, the processor jumps to that position of memory and starts executing. You could say that all methods and functions are 'global', and any indication of the contrary is a convenient illusion enforced by the compiler.

Obviously, a method acts like it belongs to a specific object, so clearly there is more going on. To tie a particular call of a method (a function) to a specific object, every member method has a hidden argument that is a pointer to the object in question. The member is hidden in that you don't add it to your C++ code yourself, but there is nothing magical about it -- it's very real. When you say this:

void CMyThingy::DoSomething(int arg);
{
    // do something
}

The compiler really does this:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

Finally, when you write this:

myObj.doSomething(aValue);

the compiler says:

CMyThingy_DoSomething(&myObj, aValue);

No need for function pointers anywhere! The compiler knows already which method you are calling so it calls it directly.

Static methods are even simpler. They don't have a this pointer, so they are implemented exactly as you write them.

That's is! The rest is just convenient syntax sugaring: The compiler knows which class a method belongs to, so it makes sure it doesn't let you call the function without specifying which one. It also uses that knowledge to translates myItem to this->myItem when it's unambiguous to do so.

(yeah, that's right: member access in a method is always done indirectly via a pointer, even if you don't see one)

(Edit: Removed last sentence and posted separately so it can be criticized separately)

Selfpollination answered 19/9, 2008 at 13:1 Comment(2)
A struct has public access to both base classes and members. Members are not necessarily "stored in sequence", layout of a non-POD class may be different to the actual "declaration order". Finally, being super picky, a static member does have an implicit object parameter but it is discarded.Turgent
"a static member does have an implicit object parameter" Hug?Kinglet
T
12

Non virtual member functions are really just a syntactic sugar as they are almost like an ordinary function but with access checking and an implicit object parameter.

struct A 
{
  void foo ();
  void bar () const;
};

is basically the same as:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

The vtable is needed so that we call the right function for our specific object instance. For example, if we have:

struct A 
{
  virtual void foo ();
};

The implementation of 'foo' might approximate to something like:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}
Turgent answered 19/9, 2008 at 12:49 Comment(0)
M
3

The virtual methods are required when you want to use polymorphism. The virtual modifier puts the method in the VMT for late binding and then at runtime is decided which method from which class is executed.

If the method is not virtual - it is decided at compile time from which class instance will it be executed.

Function pointers are used mostly for callbacks.

Manor answered 19/9, 2008 at 12:9 Comment(0)
M
1

If a class with a virtual function is implemented with a vtable, then a class with no virtual function is implemented without a vtable.

A vtable contains the function pointers needed to dispatch a call to the appropriate method. If the method isn't virtual, the call goes to the class's known type, and no indirection is needed.

Marsiella answered 19/9, 2008 at 12:10 Comment(0)
F
1

For a non-virtual method the compiler can generate a normal function invocation (e.g., CALL to a particular address with this pointer passed as a parameter) or even inline it. For a virtual function, the compiler doesn't usually know at compile time at which address to invoke the code, therefore it generates code that looks up the address in the vtable at runtime and then invokes the method. True, even for virtual functions the compiler can sometimes correctly resolve the right code at compile time (e.g., methods on local variables invoked without a pointer/reference).

Fustanella answered 19/9, 2008 at 12:36 Comment(0)
S
1

(I pulled this section from my original answer so that it can be criticized separately. It is a lot more concise and to the point of your question, so in a way it's a much better answer)

No, there are no function pointers; instead, the compiler turns the problem inside-out.

The compiler calls a global function with a pointer to the object instead of calling some pointed-to function inside the object

Why? Because it's usually a lot more efficient that way. Indirect calls are expensive instructions.

Selfpollination answered 19/9, 2008 at 13:12 Comment(0)
A
0

There's no need for function pointers as it cant change during the runtime.

Alexandriaalexandrian answered 19/9, 2008 at 12:7 Comment(0)
E
0

Branches are generated directly to the compiled code for the methods; just like if you have functions that aren't in a class at all, branches are generated straight to them.

Eggcup answered 19/9, 2008 at 12:7 Comment(0)
O
0

The compiler/linker links directly which methods will be invoked. No need for a vtable indirection. BTW, what does that have to do with "stack vs. heap"?

Owing answered 19/9, 2008 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.