Part B - Foundations

Member Functions and Privacy

Introduce member functions into the definition of a structure
Control accessibility to the data members within an instance of a structure
Introduce the concept of an object's empty state

"Because different structures can have member functions with the same name, we must specify the structure name when defining a member function" (Stroustrup, 1997)

Member Functions | Privacy | Empty State | Inlining | Summary | Exercises


A major enhancement that the C++ language introduced to C was to include logic within the description of a compound type.  C++ lets us add functions as members of a compound type, alongside its data members.  While data members hold information about an object's state, member functions define the operations performable on that state. 

This chapter describes the syntax for declaring and defining member functions and for limiting accessibility to select members of a compound type. 


Member Functions

Member functions provide the communication links between a client and an instance of a compound type.  The client calls the compound type's member functions to access and manipulate the data within an instance of the compound type. 

c plus plus structures

We classify member functions under two mutually exclusive categories:

  • query - report the state of the object
  • modifier - change the state of the object

communication

Every member function has direct access to all of the members of its compound type.  It receives and passes information between the client and the object through its parameters and return value. 

For example, consider a Student type that is defined by

 struct Student {
     int no;
     char grade[14];
 };

Add a Member Function

Declaration

To identify a function as a member, we insert its prototype into the definition of its compound type. 

For example, to add display() as a member of our Student type, we insert its full prototype:

 struct Student {
     int no;
     char grade[14];

     void display() const; // member function  
 };

The const qualifier identifies this member function as a query.  display() cannot change the state of any Student object.  That is, it cannot change either no or any character in grade

As a member function, display() has direct access to the variables no and grade

Definition

We define display() in the implementation file as follows:

 void Student::display() const {

     cout << no << ' ' << grade;

 }

This definition consists of four elements:

  • the Student:: prefix on the function name identifies it as a member of our Student type
  • the empty parameter list - this function does not receive any values from or pass any values through the parameter list to the client
  • the const qualifier identifies this function as a query - it cannot change any of the values of the data members in an instance of Student type
  • the data members - the function can access no and grade, which are defined outside the function but within the definition of the compound type

Call a Member Function

The client calls a member function in the same way that it refers to a data member of an instance of a compound type.  The call consists of the object's name, the . operator and the member's name.

For example, if harry is an object of Student type, we can display its data by calling display() on harry as follows:

 Student harry = {975, {'A','B','C'}}; 
 harry.display();
 cout << endl;

The object part of the function call identifies the data that the function accesses. 

Member Functions are not Global Functions

Global functions differ from member functions of the same name.  A client calls a global function without referring to any object.  On the other hand, a client can only call a member function on an object of some type.  A client cannot call a member functions that is not attached to an compound type. 

Defining a global function with the same name as a member function does not introduce a conflict, since calls to each involve different syntax. 

 display();       // calls the global display function
 harry.display(); // calls the display member function on harry 

Calling a Global Function from within a Member Function

Calling a global function from within member function of a compound type that contains a member function with the same name as the global function requires special attention.  To call the global function we apply the scope resolution operator in the call to the global function.  To call the member function, we simply call the function's identifier: 

 void Student::foo() {
     ::display(); // calls the global display function 
     display();   // calls the display member function
 }

Privacy

Defining a compound type using the struct keyword exposes the data members to the client.  To limit accessibility, C++ lets us introduce a layer of privacy.  To hide the information stored in any member, we can classify that member as private. 

The only members that a client needs to access are the compound type's communication links.  These links are the parameters and return values of the type's member function.  The client does not need to see the data that describes an object's state. 

Accessibility Labels

To prohibit external access to any member (data or function), we insert the label private into the definition of our compound type:

 private:

private identifies all subsequent members listed in the definition as inaccessible.

To allow client access, we insert the label public:

 public:

public identifies all subsequent members listed in the definition as accessible.

For example, if we choose to

  • hide the data members of each Student object
  • expose the member function of the Student type

we define the Student type as follows

 struct Student {
   private:
     int no;
     char grade[14];
   public:
     void display() const;
 };

With this definition of our Student type, any attempt to access a data member of a Student object generates a complier error:

 void foo(const Student& harry) {

     cout << harry.no;  // ERROR - member is private!  

 }

The function foo() can only access the data stored in harry indirectly through public member function display()

 void foo(const Student& harry) {

     harry.display();

 }

Modifying Private Data

If the data members within a compound type are private, the client cannot initialize their values directly.  in such cases, we need a separate member function that can modify the data within an object.

For example, to store data in Student objects, let us introduce a public member function named set():

 struct Student {
   private:
     int no;
     char grade[14];
   public:
     void set(int n, const char* g); 
     void display() const;
 };

set() is a modifier: its declaration does not include the const keyword.

set() receives from the client a student number and the address of a C-style string that contains a set of grades and stores this information in the object's data members:

 void Student::set(int n, const char* g) {
     int i;

     no = n; // store the Student number as received

     // store the grades as received within the available space 
     for (i = 0; g[i] != '\0' && i < 13; i++) {
         grade[i] = g[i];
     grade[i] = '\0';        // set the last byte to null
 }

Communications Links

The set() and display() member functions are now the only communication links to a client.  Any client can call set() or display() on any Student object, but no client can access to the data stored within any Student object directly.

For example,

 Student harry;

 harry.set(975, "ABC");
 harry.display();

 cout << harry.no; // ERROR .no IS PRIVATE! 

Empty State

with hidding data members, we can control which data to accept and which to reject from a client.  That is, we can check if the received values are valid before accepting them.  If the values are invalid, we can reject them and store values that represent an empty state

Upgrade set()

Let us upgrade our set() member function to accept external data only if the student number is positive-valued and the grades are A, B, C, D or F, without exception.  If the data received from the client fails to meet all of these conditions, let us ignore that data and store values that represent an empty state:

 void Student::set(int n, const char* g) {
     int i;
     bool valid = true; // assume valid input, check invalidity

     if (n < 1)
         valid = false;
     else {
         for (i = 0; g[i] != '\0' && valid; i++)
             valid = g[i] >= 'A' && g[i] <= 'F' && g[i] != 'E';

     if (valid) {
         // accept client's data
         no = n;
         for (i = 0; g[i] != '\0' && i < 13; i++)
             grade[i] = g[i];
         grade[i] = '\0';  // set the last byte to the null byte 
     }
     else {
         // ignore client's data, set an empty state
         no = 0;
         grade[0] = '\0';
     }
 }

The validation logic added to our upgraded set() function ensures that the data stored in a Student object is either valid data or data that represents an empty state

Upgrading display()

To complete this enhancement, let us upgrade our display() member function to ensure that it executes gracefully if our object happens to be in an empty state. 

 void Student::display() const {

     if (no > 0)
         cout << no << ' ' << grade;
     else
         cout << "no data available"; 
 }

Completing the Upgrade

Note that this upgrade still leaves the data in a Student object undefined before the first call to set().  To complete the upgrade and fill this hole, we will introduce a special member function in the chapter entitled Classes


Inlining

A member function that does not contain any iterations may be a candidate for inlining, which is a technique that improves the execution time for a function call at the cost of increasing the size of the executable code.  An inline request asks the compiler to insert the body of the function at every call to the function.  The compiler, instead of storing the function once in its own dedicated region of memory and inserting instructions to transfer control to that region from each call, inserts a copy of the body at each and every call.  Although inlining creates a larger executable, it avoids the overhead of a function call.  Inlining is particularly useful with member functions that contain small blocks of code. 

The compiler determines whether or not to inline a function.  If the function has too many statements or contains an iteration, the compiler ignore the request and call the function in the usual way. 

To inline a member function, we either embed its definition within the class definition or add the keyword inline as a definition modifier. 

For example,

 // Inline Functions - Embedded
 // inline_1.h

 #include <cstring>
 using namespace std;

 class Student {
     int no;
     char grade[14];
 public:
     void set(int n, const char* g); 
     void display() const {
         cout << no << ' ' << grade;
     }
 };

 // Inline Functions - Separate
 // inline_2.h

 #include <cstring>
 using namespace std;

 class Student {
     int no;
     char grade[14];
 public:
     void set(int n, const char* g); 
     void display() const;
 };
 inline void display() const {
     cout << no << ' ' << grade;
 }

The implementation of any inline function belongs in the module's header file alongside the class definition.


Summary

  • a C++ compound type may contain both data members and member functions
  • data members hold information about the state of an object
  • member functions describe the logic that an object performs on its data members
  • a query reports the state of an object without changing its state
  • a modifier changes the state of an object
  • the keyword private identifies subsequent members as inaccessible to clients
  • the keyword public identifies subsequent members as accessible to clients
  • an empty state is the set of data values that represents an absence of valid data in an object
  • a compiler replaces each call statement to an inline function with a copy of the body of the function

Exercises




Previous Reading  Previous: Compound Types II Next: Input and Output Examples   Next Reading


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