|
Templates in C++
An Introduction for Java Programmers |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
Object which makes it
fairly easy to design generic (though not type-safe)
classes
template<class AnyObject>
AnyObject max(AnyObject a, AnyObject b)
{
if (a > b) return a;
return b;
}
max returns an instance of
AnyObject and is passed two instances of
AnyObject, one named a and one named
b
AnyObject is not an actual class,
it is a "placeholder"FixedSizeCollection:
template<class AnyObject>
class FixedSizeCollection {
public:
.
.
private:
AnyObject data[BLOCK_SIZE];
.
.
};
FixedSizeCollection:
FixedSizeCollection<int> v;
#include the source code.h) and
implementation (.cpp) files but
#include the implementation (.cpp) file
(which will #include the .h file
as before).
.cpp file can be compiled and linked.
.h)
and implementation (.cpp) files for
template classes/functions -- put everything in the header
file.
.h
file contains a template so we will use .hpp
files for this purpose.
#ifndef edu_jmu_cs_IndexOutOfBoundsException_h
#define edu_jmu_cs_IndexOutOfBoundsException_h
/**
* An IndexOutOfBoundsException can be thrown by a function that is
* asked to access an element of a Vector that is "out of bounds".
*
* @author Prof. David Bernstein, James Madison University
*
*/
class IndexOutOfBoundsException {
public:
// The index causing the problem
int index;
/**
* Default Constructor
*/
IndexOutOfBoundsException();
/**
* Explicit Value Constructor
*
* @param i The index that cause the problem
*/
explicit IndexOutOfBoundsException(int i);
};
#endif
FixedSizeCollection Template#ifndef edu_jmu_cs_FixedSizeCollection_hpp
#define edu_jmu_cs_FixedSizeCollection_hpp
#define BLOCK_SIZE 100
#include "IndexOutOfBoundsException.h"
#include <stdlib.h>
/**
* A collection that can't be re-sized
*
* Note: This is not a useful class. It is, however,
* an illustration of how to use templates.
*
* @author Prof. David Bernstein, James Madison University
*/
template<class AnyObject>
class FixedSizeCollection {
public:
/**
* Default Constructor
*/
FixedSizeCollection(void);
/**
* Destructor
*/
~FixedSizeCollection(void);
/**
* Append an element to the end of the FixedSizeCollection
*
* @param e The element to append
*/
void append(const AnyObject e);
/**
* Access an element in the FixedSizeCollection
*
* @param index The index of the element of interest
* @return The element at the given index
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
AnyObject elementAt(const int index);
/**
* Get the number of used/actual elements
*/
int length(void);
private:
AnyObject data[BLOCK_SIZE];
int usedLength;
};
template<class AnyObject>
FixedSizeCollection<AnyObject>::FixedSizeCollection(void) {
for (int i = 0; i < BLOCK_SIZE; i++) {
data[i] = NULL;
}
usedLength = 0;
}
template<class AnyObject>
FixedSizeCollection<AnyObject>::~FixedSizeCollection(void) {
// Nothing to do since no memory was allocated
}
template<class AnyObject>
void FixedSizeCollection<AnyObject>::append(const AnyObject e) {
if (usedLength >= BLOCK_SIZE)
throw IndexOutOfBoundsException(usedLength);
data[usedLength] = e;
usedLength++;
}
template<class AnyObject>
AnyObject FixedSizeCollection<AnyObject>::elementAt(const int index) {
// Check the bounds of the index
if ((index < 0) || (index >= usedLength))
throw IndexOutOfBoundsException(index);
return data[index];
}
template<class AnyObject>
int FixedSizeCollection<AnyObject>::length(void) {
return usedLength;
}
#endif
/**
* An example that uses a FixedSizeCollection.
*
* @author Prof. David Bernstein, James Madison University
*/
#include "FixedSizeCollection.hpp"
#include <iostream>
#include "IndexOutOfBoundsException.h"
using namespace std;
/**
* The entry point of the example
*/
int main(void) {
int i, n;
FixedSizeCollection<int> v;
try {
n = 3;
for (i = 0; i < n; i++) {
v.append(i);
}
for (i = 0; i < (v.length()); i++) {
n = v.elementAt(i);
cout << "Element " << i << " is: " << n << endl;
}
i = 5;
n = v.elementAt(i);
cout << "Element " << i << " is: " << n << endl;
} catch (IndexOutOfBoundsException &be) {
cout << "Index out of bounds: " << be.index << endl;
}
}
#ifndef edu_jmu_cs_Table_hpp
#define edu_jmu_cs_Table_hpp
#include <stdexcept>
using namespace std;
// Prototype of the Table class
// (so that it can be used in the friend prototypes)
template<int R, int C> class Table;
// Prototypes of friend functions
template<int RT, int CTB, int RB>
Table<RT + RB, CTB> operator+(const Table<RT, CTB>& a, const Table<RB, CTB>& b);
/**
* An encapsulation of a Table.
*
* @tparam ROWS The number of rows
* @tparam COLUMNS The number of columns
*/
template<int ROWS, int COLUMNS>
class Table {
protected:
double values[ROWS][COLUMNS];
/**
* Set the value of all elements in this Table to the given value.
*
* @param value The value to assign to every element
*/
void setValues(double value);
/**
* Set the value of all elements in this Table to the value
* of the corresponding elements in another Table.
*
* @param other The other Table
*/
void setValues(const Table& other);
public:
/**
* Default Constructor.
*/
Table<ROWS, COLUMNS>();
/**
* A copy constructor.
*
* @param original The Table to copy
*/
Table<ROWS, COLUMNS>(const Table<ROWS, COLUMNS>& original);
/**
* Get a particular element of this Table.
*
* @param r The row index
* @param c The column index
* @throws out_of_range if r or c are out of bounds
* @return The value of the element
*/
double get(int r, int c) const;
/**
* Get a particular element of this Table if it contains
* a single row.
*
* @param i The index
* @throws out_of_range if i is out of bounds
* @throws length_error if this is neither a single row nor single column
* @return The value of the element
*/
double get(int i) const;
/**
* Assign another Table to this Table.
*
* Note: This method is not void so that one can write x = y = z
* (which first assigns z to y and then assigns the result of that
* assignment to x). It returns the result by reference because there
* is no concern that this will not refer to something.
*
* @param other The Table to copy
* @return The Table referred to by this
*/
Table<ROWS, COLUMNS>& operator=(const Table<ROWS, COLUMNS>& other);
/**
* Concatenate a and b (i.e., create a Table in which the
* rows of a are "above" the rows of b)
*
* Note: The friend function operator+ is one-to-one with the class.
* That is, there is an operator+ that corresponds to Table<2,2>, an
* operator+ that corresponds to Table<3,3>, etc...
*
* @tparam RT The number of rows in the top
* @tparam CTB The number of columns in both the top and bottom
* @tparam RB The number of rows in the bottom
* @param a The "top" Table
* @param b The "bottom" Table
* @return The concatenation of a and b
*/
template<int RT, int CTB, int RB>
friend Table<RT + RB, CTB> operator+(const Table<RT, CTB>& a,
const Table<RB, CTB>& b);
};
template<int R, int C>
Table<R, C>::Table() {
setValues(0.0);
}
template<int R, int C>
Table<R, C>::Table(const Table<R, C>& original) {
setValues(original);
}
template<int RT, int CTB, int RB>
Table<RT + RB, CTB> operator+(const Table<RT, CTB>& a,
const Table<RB, CTB>& b) {
Table<RT + RB, CTB> result;
// First copy the rows of a
for (int r = 0; r < RT; r++) {
for (int c = 0; c < CTB; c++) {
result.values[r][c] = a.values[r][c];
}
}
// Now copy the rows of b
for (int r = 0; r < RB; r++) {
for (int c = 0; c < CTB; c++) {
result.values[r + RT][c] = b.values[r][c];
}
}
return result;
}
template<int R, int C>
double Table<R, C>::get(int r, int c) const {
if ((r < 0) || (r >= R))
throw(out_of_range("r"));
if ((c < 0) || (c >= C))
throw(out_of_range("c"));
return this->values[r][c];
}
template<int R, int C>
double Table<R, C>::get(int i) const {
if (R != 1)
throw(length_error("shape"));
if ((i < 0) || (i >= C))
throw(out_of_range("i"));
return this->values[0][i];
}
template<int R, int C>
Table<R, C>& Table<R, C>::operator=(const Table<R, C>& other) {
setValues(other);
// Note: The result is the object referred to by this (which is returned
// by reference). So, we must reference this.
return *this;
}
template<int R, int C>
void Table<R, C>::setValues(double value) {
for (int r = 0; r < R; r++) {
for (int c = 0; c < C; c++) {
this->values[r][c] = value;
}
}
}
template<int R, int C>
void Table<R, C>::setValues(const Table<R, C>& other) {
// Don't self-assign! (Note: this is a reference; other is an object)
if (this != &other) {
for (int r = 0; r < R; r++) {
for (int c = 0; c < C; c++) {
this->values[r][c] = other.values[r][c];
}
}
}
}
#endif
template <class BT>
class Base
{
};
template <class T>
class Child : public Base<T>
{
};
template <class BT>
class Base
{
};
class Child : public Base<T>
{
};
class Base
{
};
template <class CT>
class Child : public Base
{
};
Container class that fixes the type of
element in a template)
Row
class that specializes a Table base class)
template <class T>
class Container
{
private:
T element
};
template <> // Note the empty parameter list
class Container<int>
{
private:
int element;
};
template <int ROWS, int COLUMNS>
class Table
{
public:
Table<ROWS,COLUMNS>();
};
template <int C>
class Row : public Table<1,C>
{
public:
Row<C>();
};
#ifndef edu_jmu_cs_Row_hpp
#define edu_jmu_cs_Row_hpp
#include "Table.hpp"
/**
* A Row in a Table.
*
* @tparam C The number of columns
*/
template<int C>
class Row : public Table<1, C> {
public:
/**
* Default Constructor.
*
* @tparam C The number of columns
*/
Row<C>();
/**
* A copy constructor.
*
* @tparam The number of columns
* @param original The Row to copy
*/
Row<C>(const Row<C>& original);
/**
* Assign a Table with one row to this Row.
*
* Note: This method is not void so that one can write x = y = z
* (which first assigns z to y and then assigns the result of that
* assignment to x). It returns the result by reference because there
* is no concern that this will not refer to something.
*
* @tparam C The number of columns
* @param other The Table to copy
* @return The Row referred to by this
*/
Row<C>& operator=(const Table<1, C>& other);
};
template<int C>
Row<C>::Row() {
this->setValues(0.0);
}
template<int C>
Row<C>::Row(const Row<C>& original) {
this->setValues(original);
}
template<int C>
Row<C>& Row<C>::operator=(const Table<1, C>& other) {
this->setValues(other);
// Note: The result is the object referred to by this (which is returned
// by reference). So, we must reference this.
return *this;
}
#endif
#ifndef edu_jmu_cs_UnitTest_hpp
#define edu_jmu_cs_UnitTest_hpp
#define TOLERANCE 0.0001
#include <stdexcept>
#include <stdlib.h>
#include <string>
using namespace std;
/**
* \file
* A collection of functions that can be used in testing.
*/
// Prototypes
template<class T, class S>
void assertEquals(T expected, S actual, const string& message);
void assertEquals(double expected, double actual, const string& message);
void assertEquals(bool expected, bool actual, const string& message);
/**
* Check to see if the actual value equals the expected value (using
* the == operator).
*
* Note: This function will work for any two classes that have an ==
* operator.
*
* @tparam T the class of the expected value
* @tparam S The class of the actual value
* @param expected The expected/correct value
* @param actual The actual/calculated value
* @param message A message that describes the assetion
* @throws invalid_argument if actual does not equal expected
*/
template<class T, class S>
void assertEquals(T expected, S actual, const string& message) {
// This logic is used in case there isn't a != operator
if (expected == actual)
return;
throw(invalid_argument(message));
}
/**
* Check to see if the expected double value equals the actual double
* value within a defined TOLERANCE.
*
* @param expected The expected/correct value
* @param actual The actual/calculated value
* @param message A message that describes the assetion
* @throws invalid_argument if expected does not equal actual
*/
void assertEquals(double expected, double actual, const string& message) {
if (abs(expected - actual) > TOLERANCE)
throw(invalid_argument(message));
}
/**
* Check to see if the expected bool value equals the actual bool
* value
*
* @param expected The expected/correct value
* @param actual The actual/calculated value
* @param message A message that describes the assetion
* @throws invalid_argument if expected does not equal actual
*/
void assertEquals(bool expected, bool actual, const string& message) {
if (expected != actual)
throw(invalid_argument(message));
}
#endif