The Orthodox Canonical Class Form (OCCF)

 < Day Day Up > 



The orthodox canonical class form is a recipe, if you will, for proper class design. At the very least, the OCCF specifies four required functions that you implement when declaring user-defined data types to ensure they behave correctly in the four basic object usage contexts. By implementing the OCCF in user-defined types you increase your situational awareness regarding object usage. In this capacity the OCCF is much more than a recipe for correct object behavior — it is a fundamental stepping stone toward an understanding of more advanced C++ idiomatic constructs. An intuitive understanding of the OCCF will empower you to write C++ classes that are better suited to play well in complex, object-oriented applications.

Four Required Functions

To support the four basic usage contexts the OCCF specifies four required functions that should be implemented for complex, user-defined data types. These are the default constructor, the copy constructor, the copy assignment operator, and the destructor. A complex user-defined type named Foo will be used to demonstrate each of these special functions. Example 17.1 gives the code for the Foo class declaration. The Foo class is a complex type in that it manages a pointer during its lifetime. The purpose of its OCCF functions then is to properly manage the pointer.

Listing 17.1: foo.h

start example
  1  #ifndef FOO_H  2  #define FOO_H  3  4  class Foo {  5    public:  6      Foo(int i = 0);  7      virtual ~Foo();  8      Foo(Foo& rhs);  9      Foo& operator=(Foo& rhs); 10      int getVal(); 11      void setVal(int i); 12    private: 13      int* iptr; 14  }; 15  #endif
end example

Default Constructor

The purpose of a constructor is to properly create new objects. A default constructor is one that can be called with no arguments. A default constructor can declare parameters but each constructor parameter must have a default value so the constructor can be called without arguments.

The default constructor for class Foo is declared on line 6. It declares one integer parameter whose default value is zero.

Destructor

The purpose of the destructor is to properly tear down objects when they are no longer needed and release any resources reserved for the object’s use during its lifetime.

The Foo destructor is declared on line 7 of example 17.1. It is declared virtual in anticipation of supporting an inheritance hierarchy.

Copy Constructor

The purpose of a copy constructor is to create new objects from existing objects. The copy constructor will declare at least one parameter that is a reference type to which the copy constructor belongs.

The Foo copy constructor is declared on line 8 of example 17.1. It declared one parameter named rhs (shorthand for right hand side) that is a reference to a Foo object. (i.e., Foo&)

Copy Assignment Operator

The purpose of a copy assignment operator is to initialize an existing object to the values supplied by another existing object.

The Foo copy assignment operator is declared on line 9 of example 17.1. It declares one parameter which is a reference to a Foo object.

Implementing Foo Class OCCF Functions

Study example 17.2 to see how each of class Foo’s special functions are implemented.

Listing 17.2: foo.cpp

start example
  1  #include "foo.h"  2  3  Foo::Foo(int i){ iptr = new int(i); }  4  5  Foo::~Foo(){ delete iptr; }  6  7  Foo::Foo(Foo& rhs){ iptr = new int(*(rhs.iptr)); }  8  9  Foo& Foo::operator=(Foo& rhs){ 10    *iptr = *(rhs.iptr); 11    return *this; 12  } 13 14  int Foo::getVal(){ return *iptr;} 15 16  void Foo::setVal(int i){ *iptr = i; }
end example

Consider Future Desired Behavior

The four special functions declared in class Foo implement the basic OCCF recipe which means Foo objects will behave predictably when they are created, copied, assigned, and destroyed. These four basic behaviors will allow Foo objects to be confidently passed as arguments to functions and returned from functions as well. If, however, Foo objects will be used in other programming contexts then other functions and operators must be supplied.

The Foo class supplies two additional functions: getVal() and setVal() to get and set the integer value to which a Foo object’s integer pointer points. The main() function given in example 17.3 shows Foo objects being created, used, and destroyed. The header file f.h contains the declaration for a function named fooFunction() that demonstrates passing Foo objects by value into a function. Examples 17.4 and 17.5 give the code for f.h and f.cpp respectively. Figure 17-1 shows the output of running example 17.3.

Listing 17.3: main.cpp

start example
  1  #include <iostream>  2  #include "foo.h"  3  #include "f.h"  4  using namespace std;  5  6  int main(){  7      Foo f0, f1(1), f2(2);  8      cout<<f0.getVal()<<endl;  9      cout<<f1.getVal()<<endl; 10      cout<<f2.getVal()<<endl; 11      cout<<"-------------"<<endl; 12      Foo f3(f2); 13      cout<<f3.getVal()<<endl; 14      f3 = f1; 15      cout<<f3.getVal()<<endl; 16      cout<<"-------------"<<endl; 17      f3.setVal(3); 18      cout<<f0.getVal()<<endl; 19      cout<<f1.getVal()<<endl; 20      cout<<f2.getVal()<<endl; 21      cout<<f3.getVal()<<endl; 22      cout<<"-------------"<<endl; 23      f3 = fooFunction(f2); 24      cout<<f3.getVal()<<endl; 25      return 0; 26  }
end example

Listing 17.4: f.h

start example
 1   #ifndef F_H 2   #define F_H 3   class Foo; 4 5   Foo& fooFunction(Foo foo); 6 7   #endif
end example

Listing 17.5: f.cpp

start example
1  #include "f.h" 2  #include "foo.h" 3  #include <iostream> 4  using namespace std; 5 6  Foo& fooFunction(Foo foo){ 7       cout<<"fooFunction() called: "<<foo.getVal()<<endl; 8       return foo; 9  }
end example

click to expand
Figure 17-1: Results of Running Example 17.3

Extending Foo To Participate In Other Contexts: Overloading More Operators

The key to making Foo objects behave well in their expected usage contexts is to overload the required operators. To demonstrate, I will modify class Foo so that Foo objects can be compared against each other. To help in this example I will revisit and rewrite some code originally presented in chapter 9. I will use the dumbSort() function along with the two callback functions compareAscending() and compareDescending(), rewritten as template functions. Example 17.6 gives the template versions of the dumbSort(), compareAscending(), and compareDescending() functions.

Listing 17.6: dumbsort.h (template version)

start example
  1  #ifndef DUMBSORT_H  2  #define DUMBSORT_H  3  4  template<class T>  5  bool compareAscending(T a, T b){return a>b;}  6  7  template<class T>  8  bool compareDescending(T a, T b){return a<b;}  9 10  template<class T, class U> 11  void dumbSort(T a[], int l, int r, bool (*sortDirection)(U, U)){ 12      for(int i = l; i< r; i++){ 13        for(int j = (l+1); j < r; j++){ 14          if(sortDirection(a[j-1], a[j])) { 15            U temp = a[j-1]; 16            a[j-1] = a[j]; 17            a[j] = temp; 18          } 19        } 20      } 21  } 22  #endif
end example

The template versions of dumbSort(), compareAscending() and compareDescending() can now be used to sort not only an array of Foo objects, but an array of any objects that can be compared using the greater-than or less-than operators. As an exercise, compare the template version of dumbSort() with the original given in example 9.53 in chapter 9. It did not require much effort to convert it into a template function. And because the Foo class was designed using the OCCF, the comparisons and assignments occurring within the body of the dumbSort() function can be relied upon to work properly without customizing them to work specifically with Foo objects.

Example 17.7 gives the modified Foo class declaration showing the addition of the required overloaded comparison operators.

Listing 17.7: foo.h (modified)

start example
  1  #ifndef FOO_H  2  #define FOO_H  3  4  class Foo {  5    public:  6      Foo(int i = 0);  7      virtual ~Foo();  8      Foo(Foo& rhs);  9      Foo& operator=(Foo& rhs); 10      int getVal(); 11      void setVal(int i); 12      bool operator<(Foo& rhs); 13      bool operator>(Foo& rhs); 14    private: 15      int* iptr; 16  }; 17  #endif
end example

Example 17.8 gives their implementation.

Listing 17.8: foo.cpp (modified)

start example
  1  #include "foo.h"  2  3  Foo::Foo(int i){ iptr = new int(i); }  4  5  Foo::~Foo(){ delete iptr; }  6  7  Foo::Foo(Foo& rhs){ iptr = new int(*(rhs.iptr)); }  8  9  Foo& Foo::operator=(Foo& rhs){ 10    *iptr = *(rhs.iptr); 11    return *this; 12  } 13 14  int Foo::getVal(){ return *iptr; } 15 16  void Foo::setVal(int i){ *iptr = i; } 17 18  bool Foo::operator<(Foo& rhs){ return (*iptr) < (*rhs.iptr);} 19 20  bool Foo::operator>(Foo& rhs){ return (*iptr) > (*rhs.iptr);}
end example

Example 17.9 gives the main() function showing the template version of dumbSort() and the template callback functions in action sorting an array of Foo objects in ascending and descending order.

Listing 17.9: main.cpp

start example
  1  #include <iostream>  2  using namespace std;  3  #include "dumbsort.h"  4  #include "foo.h"  5  6  int main(){  7       Foo foo_array[12];  8  9       foo_array[0].setVal(5); 10       foo_array[1].setVal(1); 11       foo_array[2].setVal(9); 12       foo_array[3].setVal(2); 13       foo_array[4].setVal(22); 14       foo_array[5].setVal(3); 15       foo_array[6].setVal(7); 16       foo_array[7].setVal(34); 17       foo_array[8].setVal(6); 18       foo_array[9].setVal(8); 19       foo_array[10].setVal(84); 20       foo_array[11].setVal(12); 21 22       for(int i=0; i<12; i++){ 23           cout<<foo_array[i].getVal()<<" "; 24       } 25       cout<<endl; 26 27       dumbSort<Foo, Foo>(foo_array, 0, 12,  compareAscending); 28 29       for(int i=0; i<12; i++){ 30           cout<<foo_array[i].getVal()<<" "; 31       } 32       cout<<endl;   33 34       dumbSort<Foo, Foo>(foo_array, 0, 12,  compareDescending); 35 36       for(int i=0; i<12; i++){   37           cout<<foo_array[i].getVal()<<" "; 38       } 39       cout<<endl; 40 41       return 0; 42  }
end example

An important point to note about the use of the dumbSort() template function is the explicit template specialization call syntax used on lines 27 and 34 of example 17.9. Figure 17-2 shows the results of running example 17.9.

click to expand
Figure 17-2: Results of Running Example 17.9

Quick Review

When designing class types implement the four basic OCCF functions plus any additional functions or overloaded operators necessary to ensure correct object behavior in anticipated object usage contexts.



 < Day Day Up > 



C++ for Artists. The Art, Philosophy, and Science of Object-Oriented Programming
C++ For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504028
EAN: 2147483647
Year: 2003
Pages: 340
Authors: Rick Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net