Friday, March 07, 2003

Writing generic functions -- cont'd (K&M Chapter 8)

A generic max function


#include <iostream>
#include <string>

using std::endl;
using std::cout;
using std::string;

template <class T>
T my_max(const T &left, const T &right)
{	
	return left > right ? left : right;
}

int main()
{
	string	s1 = "hello", s2 = "world";

	cout << my_max(12, 15) << endl;
	cout << my_max(1232.43, 54.2) << endl;
	cout << my_max(s1, s2) << endl;

	// Error: parameters are of different type
	// cout << my_max(12, 15.0) << endl;

	return 0;
}


A generic find function


// This file contains the implementation of the find function.
// This function is not actually used elsewhere in the book.

template <class In, class X>
In find(In begin, In end, const X& x)
{
	if (begin == end || *begin == x)
		return begin;
	begin++;
	return find(begin, end, x);
}



The above generic function uses an input iterator. In C++ there are five categories of iterators. They are summarized on p.154 of K&M:

Defining new types (K&M Chapter 9)

Header file for the a new Student_info type


#ifndef GUARD_Student_info
#define GUARD_Student_info

#include <string>
#include <vector>

class Student_info {
public:
	Student_info();              // construct an empty `Student_info' object
	Student_info(std::istream&); // construct one by reading a stream
	std::string name() const { return n; }
	bool valid() const { return !homework.empty(); }

	// as defined in 9.2.1/157, and changed to read into
	// `n' instead of `name'
	std::istream& read(std::istream&);

	double grade() const;    // as defined in 9.2.1/158
private:
	std::string n;
	double midterm, final;
	std::vector<double> homework;
};

bool compare(const Student_info&, const Student_info&);

#endif



Implementation file for the the new Student_info type


#include <iostream>
#include <vector>

#include "grade.h"
#include "Student_info.h"

using std::istream;
using std::vector;

double Student_info::grade() const
{
	return ::grade(midterm, final, homework);
}

bool compare(const Student_info& x, const Student_info& y)
{
	return x.name() < y.name();
}

Student_info::Student_info(): midterm(0), final(0) { }

Student_info::Student_info(istream& is) { read(is); }	

// read homework grades from an input stream into a `vector<double>'
istream& read_hw(istream& in, vector<double>& hw)
{
	if (in) {
		// get rid of previous contents
		hw.clear();

		// read homework grades
		double x;
		while (in >> x)
			hw.push_back(x);

		// clear the stream so that input will work for the next student
		in.clear();
	}
	return in;
}

istream& Student_info::read(istream& in)
{
	in >> n >> midterm >> final;
	read_hw(in, homework);
	return in;
}



main program that uses the Student_info type


#include <algorithm>
#include <iomanip>

#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

#include "Student_info.h"
#include "median.h"

using std::cin;                    using std::cout;
using std::domain_error;           using std::endl;
using std::setprecision;           using std::setw;
using std::sort;                   using std::streamsize;
using std::string;                 using std::vector;
using std::max;

int main()
{
	vector<Student_info> students;
	Student_info record;
	string::size_type maxlen = 0;

	// read and store the data
	while (record.read(cin)) {                           // changed
		maxlen = max(maxlen, record.name().size());      // changed
		students.push_back(record);
	}

	// alphabetize the student records
	sort(students.begin(), students.end(), compare);

	// write the names and grades
	for (vector<Student_info>::size_type i = 0;
	     i != students.size(); ++i) {
		cout << students[i].name()   // this and the next line changed
		     << string(maxlen + 1 - students[i].name().size(), ' ');
		try {
			double final_grade = students[i].grade();   // changed
			streamsize prec = cout.precision();
			cout << setprecision(3) << final_grade
			     << setprecision(prec) << endl;
		} catch (domain_error e) {
			cout << e.what() << endl;
		}
	}
	return 0;
}



Memory allocation in C++

The new and delete operators

In C++, dynamic memory allocation is much cleaner than in C. For example, to allocate memory for an integer, initialize it and deallocated it, we would do this in C:

int *p = (int *) malloc(sizeof(int));
*p = 42;
...
free(p);

However, in C++, the same thing can be accomplished much easier:

int *p = new int(42);
...
delete p;

You can think of the int(42) initializer as passing in the constant 42 to the int constructor. An appropriate amount of memory will be allocated for the integer and the value stored there will be initialized to 42. We could have just allocated the memory as follows:

int *p = new int;

In the above case, the value pointed to by p will be uninitialized and will likely be garbage. A value will have to be assigned to this location (e.g. *p = 42) prior to using *p in an expression.

Allocating an array

Allocating an array in C++ is also easier than allocating an array in C. For example, in C, we could use the following to allocate an array of 100 integers (note that calloc() could also be used).

int *p = (int *) malloc(sizeof(int) * 100);
...
free(p);

In C++, we can use the following syntax to allocate an array of 100 integers:

int *p = new int[100];
...
delete [] p;

Note that when we construct an array in C++, we use the new operator and specify the number of items to allocate in square brackets. When we deallocate an array in C++, we must use empty square brackets between the delete operator and the variable that points to the array that we are deallocating.

Note that in the C, the array contents are not initialized to anything in particular -- the values in the array are undefined. In the C++ case the array elements are initialized by calling the default constructor for the type being defined. In the case of integers, the array elements are uninitialized.

Exception hierarchy (not covered in K&M)

Like Java, the standard library in C++ has an exception hierarchy. At the top of this hierarchy is the exception class. This class has two major subclasses, logic_error and runtime_error (we'll be talking about subclasses in subsequent lectures).

The logic_error class has four other subclasses: length_error, domain_error, out_of_range and invalid_argument.

The runtime_error class also has three other subclasses: range_error, overflow_error and underflow_error.

In the catch clause of a try { ... } catch block, the exceptions should be listed in increasing order of generality. (i.e.subclass exceptions should be listed before their parent class exceptions). When an exception is generated, the code specified in an appropriate catch clause (if one exists) will be executed. For example, if a domain_error is thrown, it will be caught by a catch clause that catches a domain_error exception. The error will also be caught, if, instead of catching a domain_error, the catch clause catches a logic_error. This is because, in the context of the class hierarchy, a domain_error is-a logic_error

Last modified: Mon Mar 10 12:57:34 2003