Wrox Press C++ Tutorial


Private Members of a Class

Having a constructor that sets the values of the data members of a class object, but still admits the possibility of any part of a program being able to mess with what are essentially the guts of an object, is almost a contradiction in terms. To draw an analogy, once you have arranged for a brilliant surgeon such as Dr. Kildare, whose skills were honed over years of training, to do things to your insides, letting the local plumber, bricklayer or the folks from Hill Street Blues have a go hardly seems appropriate. We need some protection for our class data members.

We can get it by using the keyword private when we define the class members. Class members which are private can, in general, only be accessed by member functions of a class. There's one exception, but we'll worry about that later. A normal function has no direct means of accessing the private members of a class. This is shown here:

Having the possibility of specifying class members as private also enables you to separate the interface to the class from its internal implementation. The interface to a class is composed of the public members and the public member functions in particular, since they can provide indirect access to all the members of a class, including the private members. By keeping the internals of a class private, you can later modify them to improve performance, for example, without necessitating modifications to the code that uses the class through its public interface. To keep them safe from unnecessary meddling, it's good practice to declare data and function members of a class that don't need to be exposed as private. Only make public what is essential to the use of your class.

Try It Out - Private Data Members

We can rewrite the Box class to make its data members private.

// Ex6_06.cpp
// A class with private members
#include <iostream>
using namespace std;
class Box            // Class definition at global scope
{
   public:
      // Constructor definition 
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
      {
         cout << endl << "Constructor called.";
         length = lv;                       // Set values of
         breadth = bv;                      // data members
         height = hv;
      }

      // Function to calculate the volume of a box
      double Volume()
      {
          return length * breadth * height;
      }

   private:
      double length;   // Length of a box in inches
      double breadth;  // Breadth of a box in inches
      double height;   // Height of a box in inches
};
int main(void)
{
   Box Match(2.2, 1.1, 0.5); // Declare Match box
   Box Box2;                 // Declare Box2 - no initial values

   cout << endl
        << "Volume of Match = "
        << Match.Volume();

// Uncomment the following line to get an error
// Box2.length = 4.0;

   cout << endl
        << "Volume of Box2 = "
        << Box2.Volume();
   cout << endl;

   return 0;
}

How It Works

The definition of the class Box now has two sections. The first is the public section containing the constructor and the member function Volume(). The second section is specified as private and contains the data members. Now the data members can only be accessed by the member functions of the class. We don't have to modify any of the member functions - they can access all the data members of the class anyway. However, if you uncomment the statement in the function main(), assigning a value to the member length of the object Box2, you'll get a compiler error message confirming that the data member is inaccessible.

A point to remember is that using a constructor or a member function is now the only way to get a value into a private data member of an object. You have to make sure that all the ways in which you might want to set or modify private data members of a class are provided for through member functions.

We could also put functions into the private section of a class. In this case, they can only be called by other member functions. If you put the function Volume() in the private section, you will get a compiler error from the statements that attempt to use it in the function main(). If you put the constructor in the private section, you won't be able to declare any members of the class.

The above example generates this output:

This demonstrates that the class is still working satisfactorily, with its data members defined as having the access attribute private. The major difference is that they are now completely protected from unauthorized access and modification.

If you don't specify otherwise, the default access attribute which applies to members of a class is private. You could, therefore, put all your private members at the beginning of the class definition and let them default to private by omitting the keyword. However, it's better to take the trouble to explicitly state the access attribute in every case, so there can be no doubt about what you intend.

Of course, you don't have to make all your data members private. If the application for your class requires it, you can have some data members defined as private and some as public. It all depends on what you're trying to do. If there's no reason to make members of a class public, it is better to make them private as it makes the class more secure. Ordinary functions won't be able to access any of the private members of your class.

Accessing private Class Members

On reflection, declaring all the data members of a class as private might seem rather extreme. It's all very well protecting them from unauthorized modification, but that's no reason to keep their values a secret. What we need is a Freedom of Information Act for private members.

You don't need to start writing to your state senator to get it - it's already available to you. All you need to do is to write a member function to return the value of a data member. Look at this member function for the class Box:

inline double Box::GetLength(void)
{
   return length;
}

Just to show how it looks, this has been written as a member function definition which is external to the class. We've specified it as inline, since we'll benefit from the speed increase, without increasing the size of our code too much. Assuming that you have the declaration of the function in the public section of the class, you can use it by writing this statement:

len = Box2.GetLength();   // Obtain data member length

All you need to do is to write a similar function for each data member that you want to make available to the outside world, and their values can be accessed without prejudicing the security of the class. Of course, if you put the definitions for these functions within the class definition, they will be inline by default.

The friend Functions of a Class

There may be circumstances when, for one reason or another, you want certain selected functions which are not members of a class to be able, nonetheless, to access all the members of a class - a sort of elite group with special privileges. Such functions are called friend functions of a class and are defined using the keyword friend. You can either include the prototype of a friend function in the class definition, or you can include the whole function definition. Functions which are friends of a class and are defined within the class definition are also by default inline.

Friend functions are not members of the class, and therefore the access attributes do not apply to them. They are just ordinary global functions with special privileges.

Let's suppose that we wanted to implement a friend function in the Box class to compute the surface area of a Box object.

Try It Out - Using a friend to Calculate the Surface Area

We can see how this works in the following example:

// Ex6_07.cpp
// Creating a friend function of a class
#include <iostream>
using namespace std;

class Box            // Class definition at global scope
{
   public:
      // Constructor definition
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
      {
         cout << endl << "Constructor called.";
         length = lv;                       // Set values of
         breadth = bv;                      // data members
         height = hv;
      }

      // Function to calculate the volume of a box
      double Volume()
      {
         return length * breadth * height;
      }

   private:
      double length;    // Length of a box in inches
      double breadth;   // Breadth of a box in inches
      double height;    // Height of a box in inches

   //Friend function
   friend double BoxSurface(Box aBox);
};

// friend function to calculate the surface area of a Box object
double BoxSurface(Box aBox)
{
   return 2.0*(aBox.length*aBox.breadth +
               aBox.length*aBox.height +
               aBox.height*aBox.breadth);
}

int main(void)
{
   Box Match(2.2, 1.1, 0.5); // Declare Match box
   Box Box2;                 // Declare Box2 - no initial values

   cout << endl
        << "Volume of Match = "
        << Match.Volume();

   cout << endl
        << "Surface area of Match = "
        << BoxSurface(Match);

   cout << endl
        << "Volume of Box2 = "
        << Box2.Volume();

   cout << endl
        << "Surface area of Box2 = "
        << BoxSurface(Box2);
   cout << endl;

   return 0;
}

How It Works

We declare the function BoxSurface() as a friend of the Box class by writing the function prototype with the keyword friend at the front. Since the BoxSurface() function itself is a global function, it makes no difference where we put the friend declaration within the definition of the class, but it's a good idea to be consistent when you position this sort of declaration. You can see that we have chosen to position ours after all the public and private members of the class. Remember that a friend function isn't a member of the class, so access attributes don't apply.

The definition of the function follows that of the class. Note that we specify access to the data members of the object within the definition of BoxSurface(), using the Box object passed to the function as a parameter. Because a friend function isn't a class member, the data members can't be referenced just by their names. They each have to be qualified by the object name in exactly the same way as they might in an ordinary function, except, of course, that an ordinary function can't access the private members of a class. A friend function is the same as an ordinary function except that it can access all the members of a class without restriction.

The example produces this output:

This is exactly what you would expect. The friend function is computing the surface area of the Box objects from the values of the private members.

Placing friend Function Definitions Inside the Class

We could have combined the definition of the function with its declaration as a friend of the Box class within the class definition - the code would run as before. However, this has a number of disadvantages relating to the readability of the code. Although the function would still have global scope, this wouldn't be obvious to readers of the code, since the function would be hidden in the body of the class definition, and particularly since the function would no longer show up in the ClassView.

The Default Copy Constructor

Suppose we declare and initialize a Box object Box1 with this statement:

Box Box1(78.0, 24.0, 18.0);

We now want to create another Box object, identical to the first. We would like to initialize the second Box object with Box1.

Try It Out - Copying Information Between Instances

We can try this out with the main() function which follows:

// Ex6_08.cpp
// Initializing an object with an object of the same class
#include <iostream>
using namespace std;

class Box       // Class definition at global scope
{
   public:
      // Constructor definition
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
      {
         cout << endl << "Constructor called.";
         length = lv;                       // Set values of
         breadth = bv;                      // data members
         height = hv;
      }

      // Function to calculate the volume of a box
      double Volume()
      {
         return length * breadth * height;
      }

   private:
      double length;   // Length of a box in inches
      double breadth;  // Breadth of a box in inches
      double height;   // Height of a box in inches
};

int main(void)
{
   Box Box1(78.0, 24.0, 18.0);
   Box Box2 = Box1;           // Initialize Box2 with Box1

   cout << endl
        << "Box1 volume = " << Box1.Volume()
        << endl
        << "Box2 volume = " << Box2.Volume();

   cout << endl;
   return 0;
}

How It Works

This example will produce this:

Clearly, the program is working as we would want, with both boxes having the same volume. However, as you can see from the output, our constructor was called only once for the creation of Box1. But how was Box2 created? The mechanism is similar to the one that we experienced when we had no constructor defined and the compiler supplied a default constructor to allow an object to be created. In this case, the compiler generates a default version of what is referred to as a copy constructor.

A copy constructor does exactly what we're doing here - it creates an object of a class by initializing it with an existing object of the same class. The default version of the copy constructor creates the new object by copying the existing object, member by member.

This is fine for simple classes such as Box, but for many classes - classes that have pointers or arrays as members for example - it won't work properly. Indeed, with such classes it can create serious errors in your program. In these cases, you need to create your own class copy constructor. This requires a special approach, which we'll look into more fully towards the end of this chapter, and again in the next chapter.


© 1998 Wrox Press