Section 14h

Generic Class Study


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Managing Objects
(without knowing what they are)

Background: Generic Data

In this section, data structures that hold other data structures will be discussed. A data structure that holds other data structures is considered a container. While a quick search of the Internet will lead to a variety of other definitions (and opinions), the bottom line is that for purposes of studying data structures, and to keep things from getting too complicated, one data structure holding others is called a container. One step beyond that definition is that some programming languages allow for a data structure to hold and manipulate other data structures without knowing anything about the managed data structures. The name for data that is blindly managed by a container is generic data. The container may be able to add, insert, search for, remove, or otherwise manipulate the data but it doesn't have to know anything about the data.

So how does this work? The answer is object orientation. If the container needs to search or sort a given set of data (i.e., an object), it just asks the object how to be sorted or managed. In the previous section, the StudentMgmtClass knew to sort the data by name because it was a data structure that specifically managed StudentClass objects. However, a container doesn't know what it is managing so it must use a tool provided to it by the class it manages.

In C++, if comparison or equality needs to be tested, a comparison method and/or an overloaded operator such as < or > or = can be used but they must be declared and defined in the class being managed. Java enforces this by creating an interface with methods like compareTo. These are specified in the generic class initialization but for C++, the rules aren't explicit so it is possible that a data type being held in a container might fail if a comparison operation is attempted.

More Background: Type Parameters

In your work with functions, you used data parameters. You may pass two values into a function to be added, or a data parameter to be inserted into a list, or whatever. Now, as your learning continues, you will be introduced to type parameters. And where you passed the data values into your functions between parentheses previously, you will pass the data types into the generic class using angle brackets. Consider the following example.

SimpleVector<StudentClass> testVector;

So a data parameter goes into a function to give the function the data needed to conduct its tasks. A type parameter goes into a generic class to tell it what data type it will manage. In C++, generic classes are called templates, and this will be discussed next.

Now there is a little secret to using the generic data inside the class. In places where you want the generic data to be used, you will actually use GenericDataType as a place holder until the class instantiates objects with real data types. Once that happens, every location where the identifier GenericDataType was placed in the code will be replaced with the data type that was provided as a parameter. You will see more on this later but for the moment, you need to know that in the code you write, you can use the GenericDataType identifier for declaring variables/references of the parameter data type, and you can use it for casting. On the other hand, since C doesn't know what GenericDataType is (again, it's just a place holder), you cannot use it to instantiate arrays or other objects of that type.

Again, just like your data parameters for your functions to do their jobs, these type parameters provide a class with type information that is needed for that class to do its job. One final note before moving on: This reference uses GenericDataType as the place holder inside class implementations. However, there are others who would rather make you guess at what they are doing. These folks might use E or K or V, or other non-self-documenting quantities. Ignore these folks; they don't know anything about quality programming.

Even More Background: Keys

As discussed in the previous section, heterogenous data can have a variety of different data in one package. However, if the data is to be stored, retrieved, removed, or otherwise managed, some part of the data must be used as a reference to how the data is stored. In some systems, data might be stored in alphabetical order with respect to the names of the students. However, it could have been stored by student ID, gender, or GPA, since all of those are part of the data structure StudentClass. The designer of the class arbitrarily chose to use the name as the key since the most likely way for which the data might be searched is using the student's name.

Enough with the background. Let's look at some code.

The Class Data

Consider the initial code below.

template <class GenericDataType>
class SimpleVector
{

This is the beginning, and almost the end. The only other change in the code is that everywhere you would have put a data type, you will place GenericDataType. Here are some examples in the header file.

// copy constructor
SimpleVector( const SimpleVector &copiedVector );

// modifiers
// set value in array, without index brackets
void setValueAt( int index, const GenericDataType &item )
throw ( logic_error );

// get value in array, without index brackets
void getValueAt( int index, GenericDataType &item )
const throw ( logic_error );

private:

// data
int vectorCapacity;
int vectorSize;
GenericDataType *vectorData;

In the copy constructor, the copied object passed in is of type GenericDataType. The values passed to, and returned from, the setValueAt and getValueAt methods are of type GenericDataType, and finally the data type pointer to the actual data is again of data type GenericDataType. Once again, remember that everywhere in this code where the GenericDataType identifier is used will be replaced by the actual data type passed in as the type parameter.

Also note the use of the reference parameters in the accessor and mutator. In setValueAt, the item is passed in as a constant reference so it cannot be changed and because passing an address requires much less overhead than passing in the whole object. In getValueAt, the item is not set to constant and because of the ampersand, it will return the method result via the parameter.

Code Implementation

The difference in the code in the template is also not terribly complicated. Above every method, including the constructors, the same template description is provided as you observed at the beginning of the class declaration. Here are a couple of examples

/**
* @brief Copy constructor
*
* @details Constructs vector capacity to default and vector size to zero
* creates default size data array
*
* @param in: Other vector with which this vector is constructed *
* @note Uses copyVector to move data into this vector
*/
template <class GenericDataType>
SimpleVector<GenericDataType>::SimpleVector

(
const SimpleVector<GenericDataType> & copiedVector // in: vector to copy
)
: vectorCapacity( copiedVector.vectorCapacity ),
vectorSize( copiedVector.vectorSize ),
vectorData( new GenericDataType[ vectorCapacity ] )
{
copyVector( vectorData, copiedVector.vectorData, vectorCapacity );
}

AND

/**
* @brief vector data setting operation
*
* @details allows assignment of data directly to the vector
*
* @param in: index of element to be assigned
* @param in: data item to be stored in array
*
* @note throws logic error if index is out of bounds
*/
template <class GenericDataType>
void SimpleVector<GenericDataType>::setValueAt

(
int index, // in: index of vector element
const GenericDataType &item // in: data to be stored
)
throw ( logic_error )
{
if( index >= 0 && index < vectorCapacity )
{
vectorData[ index ] = item;
}

else
{
throw logic_error( "ERROR: Array index out of bounds" );
}
}

Again, the only difference from any other class code is the template keyword and the bracketed data type parameter in each method header.

That said, there are a couple of other items that are provided to expose you to further learning. First, the block specification looks different. This is an example of code specification that can be processed by another program to generate a complete documentation set for the class. This particular format is configured for a product called Doxygen. Another comparable product for the Java programming language is called Javadocs. These are commonly used in industry to formalize class and method documentation.

The other extension is that setValueAt method has the capacity to throw an exception if a program attempts to access data outside its boundaries. For purposes of this reference, exception handling will not be pursued but you can view how this works in this class.

A Small Caveat

Templates in C++ are a way to handle generic data; that is what was learned in this topic. However, templates are not really usable code in something comparable to the way that classes themselves are not actually run. When you develop a class, you have developed a design for an object to be instantiated. When you develop a template, you are creating a design for a class to be created. In other words, templates are to classes as classes are to objects. But since the template creates a class which then instantiates an object, the template itself doesn't actually exist in the code.

The compiler needs it to create the class but the class is what will actually create the object. Thus, the template will not be compiled. This means that it won't be in the list when you compile the program. But it has to be somewhere, right? It turns out that the template header and implementation files must be included in the program that will create the class.

However, it is really not good practice to include a .c or .cpp file in the header files part of your program. This actually does work but again, poor practice. So there is a compromise which will be used in this reference. The template implementation file will have the extension .hpp (e.g., SimpleVector.hpp). Now the .hpp file can be included in your file strictly to give the compiler a reference for creating the class(es), and you can continue to practice quality programming.

There is no video for this topic, however the code for a complete program that uses SimpleVector is provided here as a zipped file with all the files needed. To compile this, you only need to implement the following.

g++ -Wall StudentClass.cpp testprog.cpp -o test.exe

Again note that the template file is not in the compilation process but you will find it in the main program included files.

Wrapping Up Generic Data

This topic has been a somewhat brief introduction to managing generic data in C++ by creating a template. Having templates available means the code for a given container management process can be written once and used as many times as there is a need with whatever data types are needed. It is a powerful tool you may find yourself using in industry so make sure you understand the fundamentals of generic data management.