Function Types in C++

October 7th, 2009 Leave a Comment

In C++, function calls fall into four categories.

  • global functions
  • class static functions
  • non-virtual member functions
  • virtual member functions

Global functions

The global functions are the same as the function calls in C. For example,

unsigned int uiNumPowerOf2 = RoundToMultipleOfPow2(uiValue);

The address or memory location of the function call is computed at link time. So, the overhead comes from the actual jump to a different memory location and back. This affects the code cache and CPU pipeline. However, this is the simplest and fastest type of function.

Usually, the number of function calls depends on your preference. If you prefer a super fast blazing code, you would implement code with least function calls and have them only when it’s necessary. But, if you prefer readability for easy upgrading and maintenance, you should use functions liberally. Like most of our lecturers would recommend us to

  • implement our solutions into smaller sub-functions.
  • produce more readable code by having only one objective in each function.
  • reuse our small sub-functions since many problems might require them in their solutions.

However, I would advise to merge function within critical code.

Class Static Functions

They are similar to global functions with the exception that they are limited to a particular class they belongs to. They are not associated with any objects of the class. The compiler treats the class static function calls the same way as global function calls. Class static functions are used to provide better understand of the code. Here is an example of the code.

char * peerID = Peer::GetPeerID()

Non-virtual Member Functions

These functions are associated with a particular objects. For example,

Peer peer;
const char * peerID = peer.GetPeerID();

The address to call this function is determined at link time because the type of the object it is called from is also known at compile time. So, one may ask that what is the difference between this type of function and the previous ones? Well, it would be the parameter passing. We can see that in name mangling. The compiler may interpret the code into something like this.

Peer peer;
const char * peerID = __Peer__GetPeerID(&peer);

This involved transferring the extra object parameter onto the stack in 32-bit system (or in registers into 64-bit systems).

Virtual Member Functions

Virtual member functions under single inheritance differ from those under multiple inheritance. Virtual member functions are potentially the most expensive function calls. Virtual function calls are used when polymorphism is involved. Let’s look at the example below.

Peer * peer = new SuperPeer;
peer->ProcessMessage();

SuperPeer is derived from the class Peer and ProcessMessage() is a virtual member function declared in Peer class itself. Therefore, calling ProcessMessage() will trigger the virtual mechanism, and incur some extra overhead of de-referencing the vtable. The compiler may interpret the above code like this.

Peer * peer = new SuperPeer;
(peer->vptr[2])(peer);

In this example, ProcessMessage() is the second virtual function in the vtable, so the compiler invokes it by accessing the vtable and calling the function in the second slot by doing this->vptr[2](). The cost of this extra de-referencing may vary from platform to platform. This could also cause data cache misses, especially if the vtable is stored somewhere in memory far from the object itself.

However, if we invoke the function directly through the object, there will be no virtual function call. It is being treated like normal non-virtual member function call. But, we lose polymorphism feature.

Peer peer;
peer.ProcessMessage();

When the function is being called through a pointer to the object, compiler has no way of knowing the true type of the object at compile time. Therefore, the function call will go through the virtual function lookup at runtime.

Everything for virtual member functions under multiple inheritance is the same as single inheritance except for the appending of vtables of its parent classes. So, depending from which parent we inherited that virtual function, the vtable pointer might need to be offset to point to the correct section. This offset adjustment will incur a relatively minor performance cost. Unfortunately, if you make extensive use of multiple inheritance, it might result in more cache misses and slower overall performance.

It would be advisable to use multiple inheritance sparingly. Instead, replace it with containment (i.e. has-a relationship) as an alternative whenever possible.

So there you are, the some explanations for the four type of functions. I hope you they help.

Tags:
, ,

Leave a Reply


2 × two =