This is the second of two documents presenting the fundamentals of C/C++. The first document concentrated on programming features common to both C and C++, whereas this document is aimed specifically at those enhancements provided by C++.
Classes are the fundamental building block of C++ providing complex abstract data type capabilities over and above what was allowed in vanilla C. Classes give the programmer a method for encapsulating both data and functions, as well as specifying the accessibility of the members.
All classes require a constructor. The class constructor is a function which is called once for every instantiation of an object of the class' type. The class constructor is a function which has the same name as the class itself. Classes can also define a destructor which is called whenever an instantiation of the class is destroyed. Class destructors have the same name as the class but are preceded by a tilde (~).
class MyClass // declare a class called MyClass { public: MyClass(); // define a prototype for the constructor ~MyClass(); // define a prototype for the destructor };
Access to members of a class can be controlled by the keywords: public, private, and protected. When using public all other objects can access the member. When using private only members of the class, and friends of the class can access the member. Finally, the protected keyword allows private access as well as classes derived from the class to access the member. The default when an access level is not specified, is private. Constructors and destructors should always be public.
Class function members can have their actual code be in or outside of the class construct. Functions whose code is found inside of the class construct are called in-line functions and work in a macro fashion: anywhere the function is called is replaced by the code itself. Function code outside of the class construct requires the scoping operator (::) to clarify which class the function belongs to.
class MyClass { public: MyClass() { iData = 0; } // end constructor int IncrementData(); private: int iData; }; // end MyClass int MyClass::IncrementData() { iDat++; }In the above segment of code, the constructor is in-line (the code follows the declaration immediately) and the IncrementData() member function is prototyped, with the actual code appearing outside of the class specification. Notice the use of the scope operator (::) to indicate what class the function IncrementData() belongs to.
The best way of learning a new programming language is to do some coding. A few new concepts will be introduced along the way, but most of what is below can be understood from what has been covered so far. A linked list is a good example of the use of C++ abstract data types, and can be used by you when you are done learning. The following are links to the complete source files which will be presented in snippets in the sections to follow.
The first thing needed when creating a link list is some way of keeping the data. A good design is to have a class which is used for the storage, and of course for pointing to the next piece of data. This is called a container class.
// Node - container where objects in the linked list are stored class Node { public: Node( void *_pData ) { pData = _pData; pNext = NULL; } // end constructor Node *Next() { return( pNext ); } void SetNext( Node *pNode ) { pNext = pNode; } void *Data() { return( pData ); } private: void *pData; Node *pNext; }; // end NodeThis Node class uses many of the concepts which have been covered so far. The constructor for the class is in-line code, which sets the private data members to appropriate values. The constructor takes a parameter which is a pointer to a data object, and this pointer is copied to the private data member called pData. The pNext pointer is used as a pointer to the next node in the list, and as such is a pointer to a Node class instantiation. The member functions Next() and SetNext() are for getting and setting the next node in the list. The Data() function returns a pointer to the data itself. The use of a public function to access private data members is a standard abstract data type technique.
The void * pointer is a place-holder pointer which any pointer type can be converted to, this is used so that any data type can be put on the link list. The following code shows an example of converting from one pointer type to another, this is called casting.
int *pInt; void *pVoid; pVoid = (void *)pInt; // cast to a void pointer pInt = (int *)pVoid; // cast from the void pointer to a int pointerThis act of casting means a generic pointer can be stored which is irrelevant of the data type. You must be careful that you know what you are casting from since the dereference of a pointer is dependent on the type of data it points to. The compiler will not stop you from casting an integer to a character, even though these point to two different sizes of data, and will cause problems.
The definition of the linked list class is as follows.
class LList { public: LList() { pHead = NULL; pTail = NULL; pCurrent = NULL; } // end constructor ~LList() { this->destroy(); } // member functions for manipulating the list void push( void *pData ); void queue( void *pData ); void *pop(); void destroy(); // member functions for traversing the list void top() { pCurrent = NULL; } void *first() { if( pHead == NULL ) return( NULL ); return( pHead->Data() ); } // end first void *last() { if( pTail == NULL ) return( NULL ); return( pTail->Data() ); } // end last void *next(); private: Node *pHead; Node *pTail; Node *pCurrent; }; // end LListThe LList class has an in-line constructor and destructor. The data members are pointers to the Node class. Recall that the constructor for the Node class takes a parameter, and yet this parameter is not necessary here because only a pointer is being declared. The actual instantiation of the object happens is when the constructor is called, and thus that is where the parameter is necessary.
Every C++ class has an implicit pointer called the this pointer. The this pointer points to the instantiation of the object. The this pointer can be dereferenced to members of the class like any other pointer. Inside of a class declaration the use of the this pointer is implicit, so the code this->destroy(); is equivalent to destroy();.
The following code is the function definition for the member function queue().
void LList::queue( void *pData ) { Node *pNewNode; // create the new node pNewNode = new Node( pData ); // place the node in the list if( pHead == NULL ) { // list is empty pHead = pNewNode; pTail = pNewNode; } // end if -- empty list else { // list has at least one item in it pTail->SetNext(pNewNode ); pTail = pNewNode; } // end else } // end queueNotice the use of the scope operator (::) to fully define the member function name LList::queue( void *pData). Since the function is defined outside of the class construct, the scope operator is necessary to tell the linker how to match this function to that declared in the class.
This function shows an example of the new operator. The new operator is how you instantiate an object dynamically. The new operator returns a pointer to the newly created object. Notice how the constructor is used to pass in parameters for the instantiation of the object. C provides other ways of allocating memory and manipulating object instantiation, but none are as elegant as the new operator, and this should be used exclusively. The compiler automatically determines the size of the object base on it's type and allocates the appropriate amount of memory.
Paired with the new operator is the delete operator. An example of the delete operator can be found in the link list member function destroy().
void LList::destroy() { void *pData; Node *pNode, *pTempNode; pNode = pHead; while( pNode != NULL ) { pTempNode = pNode->Next(); pData = pNode->Data(); delete pData; delete pNode; pNode = pTempNode; } // end while pHead = NULL; pTail = NULL; pCurrent = NULL; } // end destroyCalling the delete operator is as simple as using the keyword delete and the pointer which points to the object to be deleted. The compiler automatically determines the amount of memory which is being released based on the type of object being deleted.
The difficulty with using void * pointers is that there is no type safety. Type-safety is the ability for the compiler to match pointers to the type of data which they are pointing to. For example it is possible to put an integer on a linked list, and then when taking it off cast it to a character. This casting would not be caught by the compiler, and can lead to very hard to find bugs. Templates allow a programmer to create objects like linked lists which are type-safe. This section uses a new version of the linked list code for examples, the full code can be found in the following links:
Templates work by allowing the instantiation of an object to specify what class of objects it can interface with. For example a templated link list could specify that it is for integers, the compiler would then catch any attempts to manipulate the list with anything but an integer. Templating is like creating separate code for each data type that you want to interface with, without the hassle. The following code is a modified version of the earlier Node class.
template <class T> class Node { public: Node( T *_pData ) { pData = _pData; pNext = NULL; } // end constructor Node *Next() { return( pNext ); } void SetNext( Node *pNode ) { pNext = pNode; } T *Data() { return( pData ); } private: T *pData; Node *pNext; }; // end NodeThis code has the new keyword template. The angle brackets tell the template directive what is being templated (in this case a class) and what to call the template object (in this case T). This Node class is very similar to the previous version, with all instances of void * pointers replaced with a pointer to the template type.
Templates work through what is called name mangling. The C++ compiler takes the names which the programmer gives classes and objects and modifies them with additional information. In the case of templates, this additional information includes the instantiations of each instance of the template. The following code shows some instantiations of the templated Node class.
Node<int> *pIntNode1; Node<int> *pIntNode2; Node<char> *pCharNode;For each new type of templated Node the compiler creates a class with a mangled name. The creation of the pointers pIntNode1 and pIntNode2 would cause a class which would be called something like Node_int, likewise the creation of the pointer pCharNode would cause a class called Node_char to be created. Any attempt to use a character with the Node_int would be flagged as an error by the compiler.
The name mangling happens underneath in the intricacies of the compiler. The advantage to templates is that you have the ability create specialized code without actually specializing it. For example a linked list specifically for integers can easily be created, and using the same code a linked list specifically for some other data type can be created just as simply. The ability to use type-safe constructs moves run-time found errors to compile time, reducing the overall software engineering effort.
The following code is a templated version of the link list class declaration.
template <class T> class LList { public: LList() { pHead = NULL; pTail = NULL; pCurrent = NULL; } // end constructor ~LList() { this->destroy(); } // member functions for manipulating the list void push( T *pData ); void queue( T *pData ); T *pop(); void destroy(); // member functions for traversing the list void top() { pCurrent = NULL; } T *first() { if( pHead == NULL ) return( NULL ); return( pHead->Data() ); } // end first T *last() { if( pTail == NULL ) return( NULL ); return( pTail->Data() ); } // end last T *next(); private: Node<T> *pHead; Node<T> *pTail; Node<T> *pCurrent; }; // end LListAgain it can be seen that this code is similar to the previous link list declaration, with the void * pointers replaced by template objects. One thing to be careful with in this code is that the LList class uses a template instance called T which takes precedence over the template instance with the same name in the Node class. The line of code Node< T > *pHead; creates a Node template instance with the same type as the instantiation of the LList class. For example, instantiation of the LList class like: LList< int > MyList; would result in the data member: Node<int> *pHead;.
WARNING!!! the g++ compiler is very picky when it comes to templated code. For the linker to resolve templated code, the class declaration and code for the class must be in the same file! The easiest way around this is to either keep all of the code in one file, or put a #include at the bottom of the declaration file which includes the code file. The second method is preferable as it allows the programmer to comment out the line when not using the g++ compiler. The sample code for the linked list takes the first approach.