Part C - Encapsulation

Helper Functions

Augment a class definition with global functions that support the class
Overload an operator as a global function that supports a class
Grant a global function access to the private members of a class

"Avoid membership fees: Where possible, prefer making functions nonmember non-friends"
(Sutter, Alexandrescu, 2005)

Independent | Operators | Friends | Summary | Exercise


The functions that support a compound type need not necessarily be member functions.  Global functions can also support a compound type.  These helper functions access objects of compound type solely through their explicit parameters.  Since helpers are not members of any compound type, they have no implicit parameters.  For a global function to help a compound type, it only needs one of its explicit paramters to refer to an object of that type.  Some programmers prefer helper functions to member functions since helpers keep the compound type's definition as compact as possible. 

This chapter describe how to define helper functions, including helper operators, and how to grant a helper privileged access to the private members of the compound type that the function supports. 


Independent Helpers

An independent helper function does not require access to the private members of the compound type that it supports.  Queries on the compound type provide the required information and keep the coupling between the helper and the compound type at a minimum. 

Comparison

Consider a comparison of two objects of the same compound type.  The helper that compares the objects returns true if their data members are identical and false otherwise.  Since the function does not change either object, a member function is not necessary and a helper will suffice.  Moreover, any call to a helper that compares two object is symmetric and hence more readable than a call to an equivalent member function. 

Example

Let us add to our Student class a helper function named areIdentical() and two queries named number() and marks(), which supply private data to the helper.  We define the queries in our Student class definition and insert the prototype for the helper function after the class definition:

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     int number() const { return no; }
     const char* marks() const { return grade; } 
 };

 bool areIdentical(const Student& lhs, const Student& rhs); 

The queries provide read-only access.  The implementation file contains the helper function's definition. 

 // Student.cpp

 #include <iostream>
 #include <cstring>
 using namespace std;
 #include "Student.h"

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n) {
     *this = Student(n, "");
 }

 Student::Student(int n, const char* g) {
     // see Current Object chapter for validation logic
     no = n;
     strcpy(grade, g);
 }

 void Student::display() const {
     cout << no << ' ' << grade;
 }

 const Student& Student::operator+=(char g) {
     int i = strlen(grade);
     if (i < M) {
         // there is room to add g
         grade[i++] = g;
         grade[i] = '\0';
     }
     return *this;
 }

 bool areIdentical(const Student& lhs, const Student& rhs) { 
     return lhs.number() == rhs.number() &&
            strcmp(lhs.marks(), rhs.marks()) == 0;
 }

The following client program calls our helper function:

 // Compare Objects
 // compare.cpp

 #include <iostream>
 using namespace std;
 #include "Student.h"

 int main () {
     Student harry(975,"AAAAA"), josee(975,"AAAAA");

     if (areIdentical(harry, josee))
         cout << "are identical" << endl;
     else
         cout << "are different" << endl;
 }











 are identical 



Note that the helper's definition is not within the scope of the Student class.  A helper function is a global function. 

A Slight Disadvantage

The disadvantage of an independent helper is that it required queries for each instance variable that it accesses.  If we add a data member to the compound type, we may also need to add a query to access the new member.  The class definition may grow with the addition of each new data member. 


Helper Operators

Helper operators are overloaded operators that are global functions.  Candidates for helper operators consist of the operators that do not change the values of thier operands; that is, +, -, *, /, ==, >, >=, <, <= and !=

Comparison

To improve the readability of our code, let us replace our areIdentical() function with an overloaded == operator that takes two Student operands.  The header file for the Student class now contains: 

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     int number() const { return no; }
     const char* marks() const { return grade; }
 };

 bool operator==(const Student&, const Student&); 

Like the definition of the helper function, the definition of this helper operator accesses the private data of each operand through queries.

 bool operator==(const Student& lhs, const Student& rhs) { 
    return lhs.number() == rhs.number() &&
           strcmp(lhs.marks(), rhs.marks()) == 0;
 }

Addition

Let us overload the + operator for a Student left operand and a char right operand to add a single grade to a copy of the Student object and return a copy of the modified object.  The header file for the Student class now contains: 

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     int number() const { return no; }
     const char* marks() const { return grade; }
 };

 bool operator==(const Student&, const Student&); 
 Student operator+(const Student&, char);

Our implementation file includes a definition that avoids the need to access the private data by calling the += operator on the temporary Student object:

 Student operator+(const Student& student, char grade) {
     Student temp = student; // makes a copy
     temp += grade;          // calls the += operator on temp 
     return temp;            // return a copy of temp
 }

For symmetry, we overload the + operator for identical operand types in reverse order.  The complete header file contains:

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     int number() const { return no; }
     const char* marks() const { return grade; }
 };

 bool operator==(const Student&, const Student&); 
 Student operator+(const Student&, char);
 Student operator+(char, const Student&);

The implementation file contains:

 // Student.cpp

 #include <iostream>
 #include <iomanip>
 using namespace std;
 #include "Student.h"

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n) {
     *this = Student(n, "");
 }

 Student::Student(int n, const char* g) {
     // see Current Object chapter for validation logic
     no = n;
     strcpy(grade, g);
 }

 void Student::display() const {
     cout << no << ' ' << grade;
 }

 const Student& Student::operator+=(char g) {
     int i = strlen(grade);
     if (i < M) {
         // there is room to add g
         grade[i++] = g;
         grade[i] = '\0';
     }
     return *this;
 }

 bool operator==(const Student& lhs, const Student& rhs) {
     return lhs.number() == rhs.number() &&
            strcmp(lhs.marks(), rhs.marks()) == 0;
 }

 Student operator+(const Student& student, char grade) {
     Student temp = student; // makes a copy
     temp += grade;          // calls the += operator on temp 
     return temp;            // return a copy of temp
 }

 Student operator+(char grade, const Student& student) {
     return student + grade; // calls operator+(const
                             //    Student&, char)
 }

The client code that uses our implementation produces the results shown on the right:

 // Helper Operator
 // helper-operator.cpp

 #include <iostream>
 using namespace std;
 #include "Student.h"

 int main () {
     Student harry(975,"AAAAA");

     harry.display();
     cout << endl;
     harry = harry + 'B';
     harry.display();
     cout << endl;
 }










 975 AAAAA


 975 AAAAAB 



Friendship

The alternative to growing a class definition with queries that provide read-only data to helper functions is to grant select helper functions direct access to the private members of the class.  Any compound type can grant a helper function access to its private members.  Granting access avoids the need for queries that are only called by helper functions. 

To grant a access, the compound type declares the function a friend.  A friendship declaration takes the form

 friend Type identifier(...);

where Type is the return type of the helper function and identifier is the function's name. 

For example:

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     friend bool operator==(const Student&, const Student&); 
 };

 Student operator+(const Student&, char);
 Student operator+(char, const Student&);

The implementation file for our Student class would look like:

 // Student.cpp

 #include <iostream>
 #include <cstring>
 using namespace std;
 #include "Student.h"

 Student::Student() {
     no = 0;
     grade[0] = '\0';
 }

 Student::Student(int n) {
     *this = Student(n, "");
 }

 Student::Student(int n, const char* g) {
     // see Current Object chapter for validation logic
     no = n;
     strcpy(grade, g);
 }

 void Student::display() const {
     cout << no << ' ' << grade;
 }

 const Student& Student::operator+=(char g) {
     int i = strlen(grade);
     if (i < M) {
         // there is room to add g
         grade[i++] = g;
         grade[i] = '\0';
     }
     return *this;
 }

 bool operator==(const Student& lhs, const Student& rhs) { 
     return lhs.no == rhs.no &&
            strcmp(lhs.grade, rhs.grade) == 0;
 }

 Student operator+(const Student &student, char grade) {
     Student temp = student; // makes a copy
     temp += grade;          // calls the += operator on temp 
     return temp;            // return a copy of temp
 }

 Student operator+(char grade, const Student &student) {
     return student + grade; // calls operator+(const
                             //    Student&, char)
 }

Note that we add the keyword friend only to the function declaration within the class definition.  We do not apply the keyword to the definition in the implementation file. 

Client code that uses our implementation doesa not change and produces the results shown on the right:

 // Friends
 // friends.cpp

 #include <iostream>
 using namespace std;
 #include "Student.h"

 int main () {
     Student harry(975,"AAAAA"), backup = harry;

     harry.display();
     cout << endl;
     harry = harry + 'B';
     harry.display();
     cout << endl;
     if (harry == backup)
         cout << "identical" << endl;
     else
         cout << "different" << endl;
 }










 975 AAAAA


 975 AAAAAB 




 different
         

Coupling

Friendship is the strongest relationship that a class can have with an outside entity.  Friendship is not exclusive.  A friend of one class may be a friend of any other class. 

Some programmers prefer to limit friendship to special cases or even better to avoid friendship altogether: the class definition only includes those helper functions that absolutely require read and write access to private members.  Helper functions that can make due with indirect access to private data members through public member functions or other helper functions remain non-friends. 

Friend Classes

A class can grant access to its private members to all of the member functions of another class.  A class friendship declaration takes the form

 friend class Identifier;

where Identifier is the name of the class to which the host class grants private access privileges. 

For example, let us grant an Administrator class access to all of the information held within a Student object.  To grant such access, we simply include a class friendship declaration within the Student class definition

 // Student.h

 const int M = 13;

 class Student {
     int no;
     char grade[M+1];
 public:
     Student();
     Student(int);
     Student(int, const char*);
     void display() const;
     const Student& operator+=(char);
     friend bool areIdentical(const Student&, const Student&); 
     friend class Administrator;
 };

No Reciprocity or Transitivity

Friendship is neither reciprocal nor transitive.  Just because one class is a friend of another class does not mean that the latter is a friend of the former.  Just because a class is a friend of another class and that other class is a friend of yet another class does not mean that the latter class is a friend of either of the others.

Consider three classes: a Student, an Administrator and an Auditor.  Let us make the Auditor a friend of the Administrator and the Administrator a friend of the Student.  Just because Auditor is a friend of Administrator and Administrator is a friend of Student, Administrator is not necessarily a friend of Auditor and Student is not necessarily a friend of Administrator (lack of reciprocity).  Moreover, just because Auditor is a friend of Administrator and Administrator is a friend of Student, Auditor is not necessarily a friend of Student (lack of transitivity).


Summary

  • a helper function is a global function that supports a compound type
  • a helper function refers to an object of the class that it supports through one of its explicit parameters
  • typically, we define an operator that does not change the value of its operands as a helper operator
  • a friend helper function has direct access to the private members of the class that granted it friendship
  • friendship is not reciprocal, transitive, or exclusive
  • non-friend helper functions reduce coupling but increase the size of the class definition

Exercises



Previous Reading  Previous: Member Operators Next: Custom iostream Operators   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo
Creative Commons License