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