web analytics

Understanding Polymorphism and Virtual Functions in C++

Options
@2016-03-05 13:15:51

Pitfall: Omitting the Definition of a Virtual Member Function

It is wise to develop incrementally. This means code a little, test a little, then code a little more, and test a little more, and so forth. However, if you try to compile classes with virtual member functions, but do not implement each member, you may run into some very-hard-to-understand error messages, even if you do not call the undefined member functions!

If any virtual member functions are not implemented before compiling, then the compilation fails with error messages similar to this:

Undefined reference to Class_Name virtual table.

Even if there is no derived class and there is only one virtual member function, but that function does not have a definition, then this kind of message still occurs.

What makes the error messages very hard to decipher is that without definitions for the functions declared virtual, there will be further error messages complaining about an undefined reference to default constructors, even if these constructors really are already defined.

Of course, you may use some trivial definition for a virtual function until you are ready to define the "real" version of the function.

This caution does not apply to pure virtual functions, which we discuss in the next section. As you will see, pure virtual functions are not supposed to have a definition.

@2016-03-05 13:27:21

How polymorphism is implemented?

The fundamental idea of polymorphism is that the C++ compiler does not know which function to call at compile-time; the appropriate function will be selected run-time. That means that the address of the function must be stored somewhere, to be looked up prior to the actual call. This `somewhere' place must be accessible from the object in question. E.g., when a Vehicle *vp points to a Truck object, then vp->getweight() calls a member function of Truck; the address of this function is determined from the actual object which vp points to.

A common implementation is the following. An object containing virtual functions holds as its first data member a hidden field, pointing to an array of pointers holding the addresses of the virtual functions. It must be noted that this implementation is compiler-dependent, and is by no means dictated by the C++ ANSI definition.

The table of addresses of virtual functions is shared by all objects of the class. It even may be the case that two classes share the same table. The overhead in terms of memory consumption is therefore:

  • One extra pointer field per object, which points to the following table;
  • One table of pointers per (derived) class to address the virtual functions.

Consequently, a statement like vp->getweight() first inspects the hidden data member of the object pointed to by vp. In the case of the vehicle classification system, this data member points to a table of two addresses: one pointer for the function getweight() and one pointer for the function setweight(). The actual function which is called is determined from this table.

The internal organization of the objects having virtual functions is further illustrated in the following figure.


Internal organization objects when virtual functions are defined.

As can be seen from above figure, all objects which use virtual functions must have one (hidden) data member to address a table of function pointers. The objects of the classes Vehicle and Auto both address the same table. The class Truck, however, introduces its own version of getweight(): therefore, this class needs its own table of function pointers.

@2016-03-05 13:29:30

Polymorphism leads to two important aspects in Object Oriented terminology - Function Overloading and Function Overriding.

  • Overloading is the practice of supplying more than one definition for a given function name in the same scope. The compiler is left to pick the appropriate version of the function or operator based on the arguments with which it is called.
  • Overriding refers to the modifications made in the sub class to the inherited methods from the base class to change their behaviour
@2016-03-07 08:01:49

How C++ Implements Virtual Functions

You need not know how a compiler works in order to use it. That is the principle of information hiding which is basic to all good program design philosophies. In particular, you need not know how virtual functions are implemented in order to use virtual functions. However, many people find that a concrete model of the implementation helps their understanding, and when reading about virtual functions in other books you are likely to encounter references to the implementation of virtual functions. So, we will give a brief outline of how they are implemented. All compilers for all languages (including C++) that have virtual functions typically implement them in basically the same way.

If a class has one or more member functions that are virtual, then the compiler creates what is called a virtual function table for that class. This table has a pointer (memory address) for each virtual member functions. The pointer points to the location of the correct code for that member function. So if one virtual function was inherited and not changed, then its table entry points to the definition for that function that was given in the parent class (or other ancestor class if need be). If another virtual function had a new definition in the class, then the pointer in the table for that member function points to that definition. (Remember that the property of being a virtual function is inherited, so once a class has a virtual function table, then all its descendent classes have a virtual function table.)

Whenever an object of a class with one or more virtual functions is created, another pointer is added to description of the object that is stored in memory. This pointer points to the classes virtual function table. When you make a call to a member function using a pointer (yep, another one) to the object, the run time system uses the virtual function table to decide which definition of a member function to use; it does not use the type of the pointer.

Of course, this all happens automatically so you need not worry about it. A compiler writer is even free to implement virtual functions in some other way so long as it works correctly (although it never actually is implemented in a different way).

  • Late binding mean that the decision of which version of a member function is appropriate is decided at runtime. In C++, member functions that use late binding are called virtual functions. Polymorphism is another word for late binding.
  • A pure virtual function is a member function that has no definition. A pure virtual function is indicated by the word virtual and the notation =0 in the member function declaration. A class with one or more pure virtual functions is called an abstract class.
  • An abstract class is a type and it can be used as a base class to derive other classes. However, you cannot create an object of an abstract class type (unless it is an object of some derived class).
  • You can assign an object of a derived class to a variable of its base class (or any ancestor class), but the member variables that are not in the base class are lost. This is known as the slicing problem.
  • If the domain type of the pointer pAncestor is a base class for the domain type of the pointer pDescendent, then the following assignment of pointers is allowed:
  • pAncestor = pDescendent;
  • Moreover, none of the data members or member functions of the dynamic variable being pointed to by pDescendent will be lost. Although all the extra fields of the dynamic variable are there, you will need virtual member functions to access them.
  • It is a good programming practice to make destructors virtual.

Comments

You must Sign In to comment on this topic.


© 2024 Digcode.com