CIS 3142 — The Canonical Class Form

CISC 3142
Programming Paradigms in C++
The C++ Canonical Class Form

Prologue - Understanding the Rules About Default Constructors, Copy Constructors, Assignment Operators, and Miranda Functions

The Miranda Rule in U.S. Law The operative portion for us is that something (i.e., an attorney) will be supplied for us if we are unable to supply one for ourselves

Default Constructors

The moral of the story is the compiler will supply functions to the class if it is possible to do so if and if doing so proves fairly useful to the programmer.

The Copy Constructor

The Assignment (=) Operator

The Dynamically Allocated Array

Here is an Array class whise underlying ('primitive') array is dynamically allocated:
class Array {
public:
	Array(int capacity) : arr(new int[capacity]), capacity(capacity), size(0) {}
private:
	int *arr;
	int capacity, size;
};
Dynamic (i.e., heap) allocation of the underlying array container allows us to: Applying what we just presented, what happens in the following cases?
Array a1(20);
…
Array a2 = a1;

void f(Array a);	// note the call by value
…
…
Array a;
…
f(a);

Array f();	// note the return by value
…
…
Array a;
…
a = f();

Array a1(10), a2(20);
…
a1 = a2;

Why a Canonical Form?

The C++ Canonical Class Form

Canonical Form for a Class

file adt.h
#ifndef ADT_H #define ATD_H #include <iostream> class ADT { friend std::ostream &operator <<(std::ostream &, const ADT &); friend std::istream &operator >>(std::istream &, ADT &); public: ADT(); ADT(const ADT &); ~ADT(); ADT &operator =(const ADT &) bool operator ==(const ADT &); ... private: ... }; #endif
file adt.cpp
#include <iostream> #include "adt.h" using namespace std; ostream &operator <<(ostream &os, const ADT &adt) { ... return os; } istream &operator >>(istream &is, ADT &adt) { ... return is; } ADT::ADT() {...} ADT::ADT(const ADT &source) {...} ADT::~ADT() {...} ADT &ADT::operator =(const ADT &rhs) { if (this == &rhs) return *this; ... return *this; } bool ADT::operator ==(const ADT &rhs) {...} #endif
Here is some more guidance on implementing the canonical form for a class as well as a class template

An Array Class

Illustrates:

A Word About Shallow vs Deep Copy

Here is a more in-depth look at the issues of aliasing and copying in Java and C++

Classes with Pointer Members

When a class contains a data member that is a pointer, there is the issue of what happens when a copy of the obkect needs to be made.

The (Orthodox) Canonical Class Form

From the above discussion, we see that a class with pointer members must typically include a copy constructor, overloaded assignment operator, and a destructor. Such a class is said to conform to the orthodox canonical class form.

The Need for a Programmer-Defined Copy Constructor

Here are two typical scenarios in which a copy constructor in invoked, and which requires a programmer-defined copy constructor for proper semantics:
vector v1;
…
vector v2 = v1;   // Copy constructor
		
or
void f(vector v2);  // Note call-by-value 

vector v1;
...
f(v1);
		
The Typical Structure of a Programmer-Defined Copy Constructor
C(const C &c) {
	// Code to copy the value of the parameter (right hand operand) to the receiver (left hand operand)
	…
}

The Need for a Programmer-Defined Assignment Operator

Here is a typical scenario in which a programmer-defined assignment operator is needed:
vector v1;
...
vector v2;
...
v2 = v1;   // assignment
The Typical Structure of a Programmer-Defined Assignment Operator
C &C(const C &c) {
	if (&c == this) return *this;		// avoid self-assignment
	// Code to perform a semantically-proper copy of the parameter's value to the 'receiver'
	// 	as well as cleanup of the old value of the receiver (left-hand operand)
	…
	return *this;
}

The Need for a Destructor

Here are the scenarios in which a destructor is needed:
void f() {
	vector v1;
	...
}     // exiting function; v1 (automatically) going out of scope
vector *vp = new vector;		// dynamically allocated vector object
...
delete vp;				// vector object destroyed (deallocated) by programmer
The Typical Structure of Programmer-Defined Destructor
~C() {
	// Code to perform any logical cleanup the object that couldn't be done by the compile
}

An Array Class in Canonical Form

The Array class is highly instructive:

array.h

array.cpp

Code Used in this Lecture