Previous Table of Contents Next


We have changed the definitions of x() and y() by inserting const between the argument list and the function body. This says two things. First, it says that if q is a const Point, it is permitted to call q.x() because doing so will not change the value of q. Second, it says that within the body of q.x, the “variable” this has type const Point* const. That is, it is a pointer to a const, and neither the pointer nor the object to which it points is allowed to change.

So, for example, if we had changed the definition of the other overloaded x function to be

   void x(int newx) const { xval = newx; }

the program would once again not compile because

   xval = newx;

would be equivalent to

   this->xval = newx;

and, because this had type const Point* const, this->xval would have type const int and therefore could not stand on the left-hand side of an assignment.

There is one more important point to mention about member functions, and that is how they affect program organization. In this little example, we have written the entire body of each member function out as part of the class definition. In practice, though, classes may have many member functions, and they may well be large. If a program has many translation units, we do not want to make the compiler read the definitions many times. Therefore, we need a way of separating the definition of the class from the definitions of its member functions, which we do along the following lines. First, we remove the bodies from the member functions, leaving only the type information. We can remove the names of the member function parameters (here newx and newy) as well. Once we have done this, we have a declaration that can appear in every translation unit that uses an object of type Point, so we put it in a separate file called, say, Point.h:

   // file Point.h
   struct Point {
       int xval, yval;
       int x() const;
       int y() const;
       void x(int);
       void y(int);
   };

Next, we take the function bodies and compile them separately:

   #include “Point.h”
   int Point::x() const { return xval; }
   int Point::y() const { return yval; }
   void Point::x(int newx) { xval = x; }
   void Point::y(int newy) { yval = y; }

When we do so, we will want to ensure that a copy of the definition of Point appears in the file that contains the function bodies—but we do that in only a single place.

Finally, there is an important implementation note. Most C++ compilers will treat a member function body that appears as part of the class definition as an implicit request to implement that member function by expanding its body inline instead of compiling a separate subroutine for it. It is therefore common practice to define classes that have tiny member functions, such as we have just seen, as part of the class definition. This strategy ensures that there is as little overhead as possible associated with using member functions; for the typical implementation, that overhead will be zero.

7.3.3. Protection

Once we have defined a Point class that has member functions to access and modify its coordinates, we might like to ensure that only those member functions are used for that purpose, and close off direct access to the xval and yval components. We can do so as follows:

   class Point{
   private
       int xval, yval;
   public:
       int x() const { return xval; }
       int y() const { return yval; }
       void x(int newx) { xval = newx; }
       void y(int newy) { yval = newy; }
   };

Because we are bringing protection into the picture, we emphasize that protection by using class rather than struct for our data structure.

The private: and public: labels say that everything that follows such a label, until the next label or the end of the class, is private or public. So in this case, if p is a Point, then any attempt to refer to p.xval or p.yval will evoke a diagnostic message from the compiler, unless that attempt is within the body of a member function of that class.

There is one other way to access private members of a class, and that is through a friend function. A friend is a function that is not a member, but has the same privileges as members. If a class has any friends, it must say what they are.

For example, we might write a function that computes the distance between two Points as follows:

   double sq(double d) { return d * d; }
   double distance(const Point& p, const Point& q)
   {
       return sqrt(sq(p.x()-q.x())+sq(p.y()-q.y()));
   }

where the sq function is useful because C++ does not have an exponentiation operator. As written, the distance function uses only the public members of Point. However, we might want to rewrite it so that it accesses the internal representation of the Point objects directly:

   double distance(const Point& p, const Point& q)
   {
       return sqrt(sq(p.xval-q.xval)+sq(p.xval-q.xval));
   }

As rewritten, this function would not compile because it uses private data in p and q. Moreover, it does not make sense to make distance a member of Point, because it acts on two Point objects and not just one. Instead, we should make it a friend:

   class Point {
       friend double distance(const Point&, const Point&);
       // as before
   };

A friend declaration can appear anywhere in a class definition, but it must appear somewhere—once for each friend function. It does not matter whether a friend declaration appears in the private, public, or protected section of a class definition; the effect is the same in each case.


Previous Table of Contents Next