CS202 Programming

Systems

 

Lecture Notes #9

 

 

 

Friends, Nesting, Static Members

 

 

 

 

 

Lecture and Reference

 

 

 

 

The Relationship between Objects of Different Classes

When we design a solution where a function or object affects the state of an object of another class, we may not want to strictly adhere to the concepts of data hiding and encapsulation. In fact, we may want to restrict the client from directly modifying an object's private data, but we may not want to make those same restrictions on other functions or classes.

Remember that with data hiding and encapsulation, we force clients to manipulate private data through public member functions. By declaring friends, we allow non-member functions or member functions of other classes access to private data. We can declare classes as well as both non-member and member functions to be friends of a class. These friends have access to an object's private members, even though the friend is not a member of that class. Friends can provide simple solutions to problems that would otherwise be unnecessarily complex.

The following sections explore the relationships between objects of different classes using the friend construct. We examine friends that are classes, friends that are non-member functions, friends that are member functions, and mutual friends. This is followed by a discussion of some practical rules to consider as we design classes with friends.

 

9.1 Friend Classes

Friend Classes: A class that has permission to access all members (even if they are protected or private) of another class.

A class can declare another class to be a friend. When we do this, the first class gives all member functions of the second class permission to access all of its protected and private information (not the other way around; a class cannot declare itself a friend of another class). However, to access those members, the friend class must have access to an object of the declaring class' type.

The following sections introduce the syntax and rules of friend classes and demonstrate their use by extending our binary tree example.

 

 

9.1.1 The Syntax of Friend Classes

By declaring an entire class as a friend, all members of the friend class have permission to access the protected and private members of the declaring class. This grants special privileges to the friend class' member functions. However, declaring a class to be a friend does not grant any privileges to functions that may be called by member functions of the friend class.

We can declare a class to be a friend by placing the following statement in the declaring class. The declaration of friend class can take the form of a comma separated list of class identifiers:

 

class declaring_class {

  friend class class_name1, //class_name1 is a friend class

               class_name2; //class_name2 is a friend class

  ...

};

 

We may place the list of friends for a particular class anywhere in the class (in the public, protected, or private sections). The effect is the same and is independent of where they are placed.

A class can be declared a friend before the friend class is defined. This is because the friend declaration only needs an incomplete declaration of the friend class. An incomplete declaration informs the compiler that the friend class may be defined later.

 

class declaring_class {   

  //class_name is not previously declared or defined, so

  //an incomplete declaration is used

  friend class class_name; //class_name is a friend class

  ...

};

 

If the friend class is already declared or defined, then the friend need only name the class and not also declare it.

 

class class_name;    //class_name is already declared

 

class declaring_class {

  friend class_name; //class_name is a friend class

  ...

};

 

9.1.2 Friend Classes Require Objects

When a class (declaring class) declares another class (friend class) to be a friend, the friend class' member functions can invoke the protected or private member functions or use the protected or private data members of the declaring class. To do so, the friend class must have access to an object of the declaring class' type.

The definition of a friend class is the same as the definition of a non-friend class. The only difference is that a friend class has special privileges that it may want to take advantage of through its member functions. However, this can only occur if an object of the declaring class' type is available to the friend class' member functions. Friend class' member functions do not have an implicit this pointer to objects of different classes!

 

9.1.3 An Example of Friend Classes

In the following example, the binary tree class has been modified to demonstrate the concept of friend classes. Each node's data members are hidden by declaring them as private. This restricts clients from accessing the emp, left, and right data members and prevents clients from creating node objects because the constructor is private. This also restricts other clients (such as our tree class) from accessing members. Since the tree class must have a means for accessing and modifying the contents of each node in the tree, it is declared a friend of node. This is shown through the following example:

 

//tree.cpp implementation (Ex1201)

#include "tree.h"

 

class node { //node for binary tree

  friend tree; //allow class tree private access

  private:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node 

};

...

 

By declaring the entire tree class as a friend, all member functions of that class have access to the private members of each node, given a direct or indirect reference to a node. This grants tree objects permission to create node objects by accessing the private constructor.

 

9.2 Friend Functions

Friend Functions: A non-member function that has permission to access all members (even if they are protected or private) of a class.

In the previous section we learned about classes specifying other classes as friends. Classes listed as friends not only have permission to access all public members but they also have permission to access all protected and private members. With friend functions, we take this same concept and apply it to individual non-member functions. Friend functions are non-member functions that have permission to access all public, protected, and private members of a class. This is particularly useful with operator overloading.

A non-member function can be declared a friend by placing the following statement in the declaring class. The declaration of a friend function takes the form of a function prototype statement, preceded by the keyword friend.

 

class declaring_class_name {

  friend return_type function_name(arg_list);

};

 

 As with friend classes, we must declare friend functions within the class in question. Declaring a non-member function as a friend allows access to the  protected and private members of the declaring class. Friend declarations may be placed anywhere in a class: in its public, protected, or private sections. The meaning is the same.

 Remember that friends are granted access to, but not a means for accessing, the members of a class. Friend functions must use an object (either as an argument, as a local, or as a global) in order to access the data members. Typically, friend functions are designed with formal arguments, where clients pass either an object, the address of an object, or a reference to an object as an argument.

 

9.3 Friend Member Functions

Friend  Member Functions: A member function of another class that has permission to access all members (even if they are protected or private) of a class.

In the previous sections we learned about classes listing other classes and non-member functions as friends. Now consider one more extension to this theme. We can list individual member functions of another class as friends.

The following sections introduce the concept of friend member functions and demonstrate their use by extending our binary tree example.

 

9.3.1 Overview of Friend Member Functions

Declaring a member function of another class as a friend grants access to the protected and private members of the declaring class. The declaring class must specify the class to which the member function belongs. This grants special privileges to only that member function. Friend member functions may be declared anywhere in a class: in its public, protected, or private sections. The meaning is the same.

We can declare a member function to be a friend by placing the following statement in the declaring class. The declaration of a friend member function takes the form of a member function prototype statement, preceded by the keyword friend. Member functions are declared using their class name followed by the scope resolution operator.

 

class declaring_class_name {

  friend return_type class_name::function_name(arg_list);

};

 

When using friend member functions, the member function must have an object of the appropriate type available to use. This may come from a formal argument, as a local object in the member function's implementation, as a data member in the member function's class, or as a global object. Having an object is necessary because our friend member functions have no implicit way of referring to members of another class' object.

A member function cannot be declared a friend before it is first declared as a member of its own class. Unlike a friend class, where we can have an incomplete declaration, friend member function declarations (in our declaring class) cannot take place until after the member functions have been declared (in their own class).

 

9.3.2 An Example of Friend Member Functions

Friend member functions may be used in our binary tree example to provide access to a node's data members. Instead of declaring the entire tree class as a friend, where all member functions of that class have access to the private members of node, we declare only the member functions that need access to a node's members as friends. Friendship is only granted to those functions that need special privileges. It is not granted to the entire class. For example, the default constructor does not need to be a friend, because the body of the constructor does not need access to the private members of a node. On the other hand, the insert member function must be a friend, because that function assigns values to the node's private data members.

 

//tree.cpp implementation (Ex1202)

#include "tree.h"

 

class node { //node for binary tree

  friend void tree::copy(node* &, const node*);

  friend void tree::destroy(node*);

  friend void tree::insert(const employee &);

  friend node* tree::add(node*, node*);

  friend void tree::traverse(const node*) const;

  private:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node 

};

 

 

9.4 Mutual Friends

If two or more classes share the same friend class or function, those classes share a mutual friend. This means that more than one class gives a single friend class or function permission to access their members. The rules that apply to friends of a single class still apply to friends that are shared between two or more classes.

The following example illustrates the use of mutual friends. In this example, we grant friendship to the tree class in both the employee and node classes. This allows us to access the salary member of the employee class directly in the tree::add member function. In some situations, this can improve execution performance and may be justified.

The tree class must be declared a friend of node. This allows the add member function to access a node's private data members (e.g., emp). In addition, the tree class must be declared a friend of employee. This allows the insert member function to access an employee's private data members (e.g.., salary). This is necessary even though the salary data member is referenced indirectly through a pointer to an employee object from a node object. With both friend declarations, the tree class becomes a mutual friend of the employee and node classes.

 

//employee.h class interface (Ex1203)

class employee { //employee class

  friend class tree; //allow tree private access to salary

  public:

    ...

};

 

//tree.cpp implementation (Ex1203)

#include "tree.h"

 

class node { //node for binary tree

  friend class tree; //allow tree private access

  private:

    ...

...

void tree::insert(const employee &emp) { //insert employee

  node* new_node = new node(emp);

  root = add(root, new_node);

}

node* tree::add(node* node_ptr, node* new_node) {

  if (node_ptr) {

    if(new_node->emp.salary < node_ptr->emp.salary)

      node_ptr->left = add(node_ptr->left, new_node);

    else

      node_ptr->right = add(node_ptr->right, new_node);

    return (node_ptr);

  }

  else

    return (new_node);

}

...

 

9.5 Practical Rules about Friends

Each class must declare all classes and functions that it wants to be friends. Classes and functions not listed as friends cannot access that particular class' protected or private information. Classes and functions cannot declare themselves to be friends of a class. That is the wrong approach. Only classes can list other classes or functions as friends.

When a class declares a class or function to be a friend, that class is giving the class or function permission to access its protected and private members. In order for a function to access those members, an object of the declaring class must be made available to the function. This could be done as an argument (passed by value, passed by reference, or as a pointer to an object), as a local object, or as a global object.

Through friends we can design solutions with cooperating classes and functions. It is possible to design friends into a class that does not violated encapsulation. This is because friends are part of the encapsulation or interface to the class. On the other hand, over use of friends may cause unintended side effects and decrease our class' maintainability. This is because we are allowing classes and functions access to data and operations that are normally not accessible outside the class itself.

 

 

9.6 Nested Classes

Nested Classes:            A class defined inside another class' definition.

We have learned that classes can have other classes as well as functions as friends. Using friends, a class' hidden information can be referenced either directly or indirectly. Now it is time to expand this discussion by nesting classes as an alternative to friends. With nesting, we place one class' definition within another. Nesting is quite different than declaring friends. Friends provide special privileges to members; whereas, nesting restricts the scope of a class' name and can significantly reduce the global namespace pollution.

The following sections introduce the concept of nested classes, demonstrate how to define and use nested classes, and outline the scope of a nested class' name.

 

9.6.1 The Objectives for Nesting Classes

When we define a class inside another class' definition, we are nesting classes. A class' definition can be placed inside the public, protected, or private sections of another class. The purpose of this is to hide the nested class' name inside another class, restricting access to that name. It does not give either the outer class or the nested class any special privileges to access protected or private members of either class. A nested class does not allow the outer class access to its hidden members. This can only be done with friends or public member functions. Nested classes follow all of the same rules as non-nested classes except that the name of the nested class is hidden.

Nesting simply limits the scope of a class' name. This means that we can create many classes of the same name, each nested inside a different class, and each with a different set of data members and member functions. This is possible since we must use the scope resolution operation to access the nested names. Used in this manner, nesting reduces the number of global names.

 

9.6.2 The Syntax for Nesting Classes

If a class is defined within another class in the public section, the nested class is available for use the same as objects defined for the outer class. Because classes are typically defined as global in our programs, the nested class as well as the outer class are available for use by clients. To define objects of a public nested class, the name of the nested class must be qualified by the outer class' name (i.e., outer_class::nested_class). Therefore, the name is hidden within the outer class. It is not in the global namespace.

 

class outer_class {

  public:

    class nested_class {

      public:

        nested_class(); //nested class' constructor

        ...

    };

    ...

};

 

//Clients can create objects by qualifying the name:

outer_class::nested_class object;

 

If a class is defined within another class in the private section, the nested class is available for use only by the member functions of the outer class and by friends of that class. This means that clients, such as our main, cannot create objects of the nested class type.

In the implementation of a nested class' member functions, where the interface is separate from the implementation, an additional class name and scope resolution operator is required.

 

//nested class' constructor implementation

outer_class::nested_class::nested_class() {

  ...

}

 

If we nest the node class within the tree class, the constructor would have the following form:

 

tree::node::node(const employee &obj) :

  emp(obj),

  left(0),

  right(0) {

  ...

}

 

9.6.3 An Example of Nested Classes

Consider adding nesting to our binary tree example. We can specify the node class as a private nested class within our tree class. By defining the node inside of the tree class and placing it in the private section, the entire node becomes a private member. This means that clients can't use the node class because it is not accessible to the client program.

By changing the node's members to be public, members within the tree class have direct access to node members without being declared as friends. The tree class' member functions can access the node's members without any special qualifiers with one exception. If a member function's return type is that of a node, we must place the return type within the scope of the class. For example, consider the add utility member function. The return type is a pointer to a node. The name node must be placed in the scope of tree:

 

tree::node* tree::add(node* node_ptr, node* new_node) {

  ...

}

 

The following example demonstrates the definition and usage of nested classes.

 

//tree.h interface (Ex1104)

#include "employee.h"

 

class tree { //binary tree class

  public:

    tree();                           //default constructor

    tree(const tree &);               //copy constructor

    ~tree();                          //destructor

    //Assignment operator should be provided 

    void insert(const employee &);    //insert employee

    void write() const;               //write employee info

  private:

    class node { //node for binary tree

      public:                         //make access public

        node() :                      //default constructor

          left(0),

          right(0) {

        }

        node(const employee &obj) :   //one arg constructor

          emp(obj),

          left(0),

          right(0) {

        }

        employee emp;                 //employee object

        node* left;                   //left child node

        node* right;                  //right child node 

    };

    void copy(node* &, const node*);  //preorder traversal

    void destroy(node*);              //postorder traversal

    node* add(node*, node*);          //add in sorted order

    void traverse(const node*) const; //inorder traversal

    node* root;                       //root node of tree

};

 

//tree.cpp implementation (Ex1204)

#include "tree.h"

 

...

void tree::insert(const employee &emp) { //insert employee

  node* new_node = new node(emp);

  root = add(root, new_node);

}

//use scope resolution operator & put node in scope of tree

tree::node* tree::add(node* node_ptr, node* new_node) {

  if (node_ptr) {

    if(new_node->emp.get_salary() <

       node_ptr->emp.get_salary() )

      node_ptr->left = add(node_ptr->left, new_node);

    else

      node_ptr->right = add(node_ptr->right, new_node);

    return (node_ptr);

  }

  else

    return (new_node);

}

...

 

Alternatively we can declare the node class as a private nested class, instead of defining it within the outer class. In this case, the declaration of the nested class represents a forward reference. To define the node class later in the same or different file, we must qualify the name by the outer class' name and the scope resolution operator:

 

class outer_class::nested class { ...};

 

//tree.h interface (Ex1105)

#include "employee.h"

 

class tree { //binary tree class

  public:

    tree();                           //default constructor

    tree(const tree &);               //copy constructor

    ~tree();                          //destructor

    //Assignment operator should be provided 

    void insert(const employee &);    //insert employee

    void write() const;               //write employee info

  private:

    class node;                       //forward reference

    void copy(node* &, const node*);  //preorder traversal

    void destroy(node*);              //postorder traversal

    node* add(node*, node*);          //add in sorted order

    void traverse(const node*) const; //inorder traversal

    node* root;                       //root node of tree

};

 

//tree.cpp implementation (Ex1205)

#include "tree.h"

 

class tree::node { //node for binary tree

  public:                       //make access public

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node

};

...

 

Practical Rules: Remember, nesting does not provide an outer class with any special access. It simply hides the nested class' name. A nested class has no special access to any of the outer class' data or member functions. And, an outer class has no special access to a nested class' data or member functions.

 

 

9.7 Relationships between Member and Non-member Functions

Additional relationships can exist between a class' member and non-member functions. For example, utility operations come in two forms: those that are member functions and those that are non-member functions. When utility operations are implemented as private member functions, they are encapsulated within a class and have access to all of an object's data members. Clients or any other classes are restricted from directly accessing these functions.

Alternatively, when utility operations are non-member functions, they can be used by more than one class. Such utility operations are granted permission to access all of an object's data members by being declared a friend of the corresponding class(es). Clients or any other classes are not restricted from directly accessing these functions, unless they are defined as non-member static functions.

The following sections introduce the concept of non-member utility operations and demonstrate them with our binary tree example.

 

 

 

9.7.1 Utility Operations as Non-member Static Functions

Implementing utility operations as non-member functions is useful in situations where the utility operations are not invoked through an object and do not need direct access to an object's data members. This is useful in situations where multiple classes benefit from the same utility operations. When this occurs, it is possible to design utility operations as non-member functions. Member functions within a class can access those utility operations through simple function calls.

We recommend that utility operations implemented as non-members be defined as static functions within the class' implementation file. Using this approach, the utility operations are encapsulated within the module in which they are defined. Static functions not only hide the implementation but also allow any member function defined within the same file direct access to those utility operations.

To implement utility functions as non-member static functions, we simply implement them as static functions and place their definitions in the class implementation file. When we do this, the prototypes for the utility operations are not placed in the class' interface. Instead, the static functions are hidden in the implementation file. A member function can simply invoke a non-member static utility function with a function call.

 

9.7.2 Using Non-member Static Functions

Consider the utility operations defined as part of the tree class: copy, destroy, add, and traverse. None of these functions need to be member functions because they do not directly access the data members of the object itself. Since these utility functions are recursive in nature, each invocation relies on its arguments to determine both the results of the operation and the depth of recursion. Therefore, they are prime candidates for being considered as non-member static functions. By implementing these operations as non-member functions, there is no way for these functions to directly access the data members of our object because there is no this pointer associated with non-member functions.

To implement these as non-member static functions, we simply implement them as static functions and place their definitions in the tree.cpp file (where the tree member functions are implemented). The prototypes for these utility operations are no longer placed in the class' interface.

The following example demonstrates non-member static utility functions. The prototypes have been removed from the class interface and the member functions have been replaced by non-member static functions in the class implementation.

 

//tree.h interface (Ex1206)

#include "employee.h"

 

class node;  //forward reference to node

class tree { //binary tree class

  public:

    tree();                        //default constructor

    tree(const tree &);            //copy constructor

    ~tree();                       //destructor

    //Assignment operator should be provided 

    void insert(const employee &); //insert employee

    void write() const;            //write employee info

  private:

    //utility functions now static local in tree.cpp file

    node* root;                    //root node of tree

};

 

//tree.cpp implementation (Ex1206)

#include "tree.h"

 

class node { //node for binary tree

  public:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node 

};

 

tree::tree() :                  //default constructor

  root(0) {

}

 

static void copy(node* &new_node, const node* old_node) {

  if(old_node) {

    new_node = new node(old_node->emp);

    copy(new_node->left, old_node->left);

    copy(new_node->right, old_node->right);

  }

}

tree::tree(const tree &tree) :  //copy constructor

  root(0) {

  copy(root, tree.root);

}

 

static void destroy(node* node_ptr) {

  if(node_ptr) {

    destroy(node_ptr->left);

    destroy(node_ptr->right);

    delete node_ptr;

  }

}

tree::~tree() {                 //destructor

  destroy(root);

}

 

static node* add(node* node_ptr, node* new_node) {

  if (node_ptr) {

    if(new_node->emp.get_salary() <

       node_ptr->emp.get_salary() )

      node_ptr->left = add(node_ptr->left, new_node);

    else

      node_ptr->right = add(node_ptr->right, new_node);

    return (node_ptr);

  }

  else

    return (new_node);

}

void tree::insert(const employee &emp) { //insert employee

  node* new_node = new node(emp);

  root = add(root, new_node);

}

 

static void traverse(const node* node_ptr) {

  if(node_ptr) {

    traverse(node_ptr->left);

    node_ptr->emp.write();

    traverse(node_ptr->right);

  }

}

void tree::write() const {      //display employees

  traverse(root);

}

 

The problem with this solution is that non-member functions are gaining access to a class' private data. Data is therefore not hidden within the class. In addition, clients can directly create node objects and manipulate the data of a node directly. Only one node data type can exist within a single program as part of the global namespace. Clients are unable to use the identifier node to represent any other type of data. The following three sections will incrementally resolve these problems.

 

9.7.3 Using Non-member Functions and Nested Classes

In this section, we combine the concepts of non-member static functions and nested classes. We hide the node class within the tree class. This allows clients to define other node types without encountering conflicts.

All of the utility operations have been defined as non-member static functions. The definition of each function occurs before the implementation of each member function, so no explicit prototype statements are needed.

We combine this with nesting by placing the declaration of the node class in the public section of our tree class. This means that the name of the node class is hidden from global scope, but it is accessible as a public member of tree if properly qualified. Therefore, the class name and the scope resolution operator must be used whenever node is accessed outside of the tree class. To provide  copy, destroy, add, and traverse with access to a node's members, the node class must be declared in the public section of the tree class.

The following example demonstrates this design:

 

//tree.h interface (Ex1207)

#include "employee.h"

 

class tree {    //binary tree class

  public:

    class node; //forward reference to node

    tree();                        //default constructor

    tree(const tree &);            //copy constructor

    ~tree();                       //destructor

    //Assignment operator should be provided 

    void insert(const employee &); //insert employee

    void write() const;            //write employee info

  private:

    node* root;                    //root node of tree

};

 

//tree.cpp implementation (Ex1207)

#include "tree.h"

 

class tree::node { //node for binary tree

  public:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node

};

 

tree::tree() :                  //default constructor

  root(0) {

}

 

static void copy(tree::node* &new_node,

                 const tree::node* old_node) {

  if(old_node) {

    new_node = new tree::node(old_node->emp);

    copy(new_node->left, old_node->left);

    copy(new_node->right, old_node->right);

  }

}

tree::tree(const tree &tree) :  //copy constructor

  root(0) {

  copy(root, tree.root);

}

 

static void destroy(tree::node* node_ptr) {

  if(node_ptr) {

    destroy(node_ptr->left);

    destroy(node_ptr->right);

    delete node_ptr;

  }

}

tree::~tree() {                 //destructor

  destroy(root);

}

 

static tree::node* add(tree::node* node_ptr,

                       tree::node* new_node) {

  if (node_ptr) {

    if(new_node->emp.get_salary() <

       node_ptr->emp.get_salary() )

      node_ptr->left = add(node_ptr->left, new_node);

    else

      node_ptr->right = add(node_ptr->right, new_node);

    return (node_ptr);

  }

  else

    return (new_node);

}

void tree::insert(const employee &emp) { //insert employee

  node* new_node = new node(emp);

  root = add(root, new_node);

}

 

static void traverse(const tree::node* node_ptr) {

  if(node_ptr) {

    traverse(node_ptr->left);

    node_ptr->emp.write();

    traverse(node_ptr->right);

  }

}

void tree::write() const {      //display employees

  traverse(root);

}

 

This solution has allowed the client to create other node classes to represent other types of data without running into naming conflicts. However, this solution still allows non-member functions to gain access to a class' private data. Data is therefore not hidden within the class. In addition, clients can directly create node objects and manipulate the data of a node.

 

9.7.4 Using Static Non-member Friend Functions

In the previous example, if the node's members had been declared as private client access would be restricted to the public members and the data would be completely hidden. To do this, the tree class would need to be declared a friend along with each of the static non-member utility functions.

By declaring the node in the private section, non-member utility functions must now be given access to the node's private data members. This means that copy, destroy, add, and traverse all need to be declared as friends of the node class. Once this is done, the non-member functions along with the class' member functions have access to the node's members.

The following example demonstrates each of these concepts.

 

//tree.h interface (Ex1208)

#include "employee.h"

 

class tree {    //binary tree class

  public:

    class node; //forward reference to node

    tree();                        //default constructor

    tree(const tree &);            //copy constructor

    ~tree();                       //destructor

    //Assignment operator should be provided

    void insert(const employee &); //insert employee

    void write() const;            //write employee info

  private:

    node* root;                    //root node of tree

    friend static void copy(node* &, const node*);

    friend static void destroy(node*);

    friend static tree::node* add(node*, node*);

    friend static void traverse(const node*);

};

 

The modifications to the previous example have allowed us to encapsulate non-member functions. This was accomplished by hiding them in a module as static local functions and making them friends of the tree class so that they had access to the necessary private members of that class. Notice that the friend declarations must occur after node is declared to be a private class of tree. Also notice that the definition must still qualify node as tree::node.

 

9.7.5 Static Functions as Members

The previous examples were useful to demonstrate alternate ways to organize and structure non-member utility functions. However, there is an easier way to solve the same problem by using static functions as members. By simply making the static utility functions members, they have direct access to the node class' public members, even if the node class is defined in the tree class' private section. This can be done without declaring our functions as friends.

In the following example, we have made copy, destroy, add, and traverse private static member functions of the tree class. These functions do not have a this pointer, just like non-member functions. However, unlike non-member functions they have permission to access private data members and are automatically hidden from client programs. Furthermore, they don't have to be declared as friends. Notice how much cleaner this is compared to the previous example.

 

//tree.h interface (Ex1209)

#include "employee.h"

 

class tree {    //binary tree class

  public:

    class node; //forward reference to node

    tree();                        //default constructor

    tree(const tree &);            //copy constructor

    ~tree();                       //destructor

    //Assignment operator should be provided 

    void insert(const employee &); //insert employee

    void write() const;            //write employee info

  private:

    node* root;                    //root node of tree

    static void copy(node* &, const node*);

    static void destroy(node*);

    static tree::node* add(node*, node*);

    static void traverse(const node*);

};

 

//tree.cpp implementation (Ex1209)

#include "tree.h"

 

class tree::node { //node for binary tree

  public:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node

};

 

tree::tree() :                  //default constructor

  root(0) {

}

 

tree::tree(const tree &tree) :  //copy constructor

  root(0) {

  copy(root, tree.root);

}

void tree::copy(node* &new_node, const node* old_node) {

  if(old_node) {

    new_node = new node(old_node->emp);

    copy(new_node->left, old_node->left);

    copy(new_node->right, old_node->right);

  }

}

 

tree::~tree() {                 //destructor

  destroy(root);

}

void tree::destroy(node* node_ptr) {

  if(node_ptr) {

    destroy(node_ptr->left);

    destroy(node_ptr->right);

    delete node_ptr;

  }

}

 

void tree::insert(const employee &emp) { //insert employee

  node* new_node = new node(emp);

  root = add(root, new_node);

}

//use scope resolution operator & put node in scope of tree

tree::node* tree::add(node* node_ptr, node* new_node) {

  if (node_ptr) {

    if(new_node->emp.get_salary() <

       node_ptr->emp.get_salary() )

      node_ptr->left = add(node_ptr->left, new_node);

    else

      node_ptr->right = add(node_ptr->right, new_node);

    return (node_ptr);

  }

  else

    return (new_node);

}

 

void tree::write() const {      //display employees

  traverse(root);

}

void tree::traverse(const node* node_ptr) {

  if(node_ptr) {

    traverse(node_ptr->left);

    node_ptr->emp.write();

    traverse(node_ptr->right);

  }

}

 

9.7.6 Generating inline Member Functions

In situations where member functions are defined (implemented) in the class interface, member functions are considered to be inline and the compiler will attempt to generate inline code. In such cases, we don't need to use the class name and the scope resolution operator.

On the other hand, when member functions are defined outside of the class interface, we must qualify them with the class name and the scope resolution operator. If we want such a member function to be compiled as inline code, we must use the keyword inline in front of the member function and define the function in the same file as the class interface. (Then, it is up to the compiler whether or not inline code is generated.)

The following syntax is necessary if we separate the class interface from the implementation and want the member functions to be inline. We must move the definition of the node class to tree.h because the new operator in insert needs a complete definition of a node to allocate memory.

 

//tree.h interface (Ex1210)

#include "employee.h"

 

class tree {    //binary tree class

  public:

    class node; //forward reference to node

    tree();                        //default constructor

    tree(const tree &);            //copy constructor

    ~tree();                       //destructor

    //Assignment operator should be provided 

    void insert(const employee &); //insert employee

    void write() const;            //write employee info

  private:

    node* root;                    //root node of tree

    static void copy(node* &, const node*);

    static void destroy(node*);

    static tree::node* add(node*, node*);

    static void traverse(const node*);

};

 

class tree::node { //node for binary tree

  public:

    node() :                    //default constructor

      left(0),

      right(0) {

    }

    node(const employee &obj) : //one arg constructor

      emp(obj),

      left(0),

      right(0) {

    }

    employee emp;               //employee object

    node* left;                 //left child node

    node* right;                //right child node

};

 

inline tree::tree() :

  root(0) {

}

 

inline tree::tree(const tree &tree) :

  root(0) {

  copy(root, tree.root);

}

 

inline tree::~tree() {

  destroy(root);

}

 

inline void tree::insert(const employee &emp) {

  node* new_node = new node(emp);

  root = add(root, new_node);

}

 

inline void tree::write() const {

  traverse(root);

}