friend & const & more Constructors

friend: Giving access to private members of a class

A class can grant a specific function (or an entire other class) access to its private members (variables and functions) by using the keyword friend in its class definition. Friendship must be granted, it cannot be taken. Additionally, it is important to note that a friend function is NOT a member of a class. Therefore:

For example:

// Class for storing times of day
class ToD {

  friend bool set12(ToD &T, int lh, int bh, string ampm);

private:
  int h, m;
  bool PM;

public:
  ToD(int lh, int bh, string ampm);

};

With the class defined this way, the function set12(ToD&,int,int,string) has access to the private data members of ToD, but nothing and nobody else does. So, the friend construct allows us to grant access to specific functions, while keeping everyone else locked out.

So, in order to use the set12 function in main we would simply do the following:

ToD time(10,30,"PM");
...
...
set12(time,11,00,"AM");
When to use a friend function?

In general, the friend feature should be used sparingly. Before implementing a friend function, you should first consider providing access via a member function. We want to practice information hiding, but if everyone is a friend then nothing is hidden! If you find yourself having an abundance of friend functions, it may be indicative of a problem with your design.

const: Protecting your data in C++

In its most basic usage const can be used in front of a variable declaration to make the variable constant, i.e. to ensure that its initialized value would be the only value it ever had.

For example:

const int theGreatestClassAtUSNA = 2001;

In other contexts, as we'll see, const is really more of a promise, one to which we'll be held, that an object won't be modified in a particular function. To understand the meaning (and importance) of const in this sense, consider the following program, which uses a version of our good friend Point:

Point.h Point.cpp
class Point {
private:
  double x, y;

public:
  Point(double a = 0, double b = 0) {
    x = a; y = b;
  }

  double getX() { return x; }

  double getY() { return y; }

  Point reflectX() {
    return Point(x,-y);
  }

  friend void readIn(Point &P);
  friend void writeOut(Point &P);
};
#include "Point.h"
#include <iostream>
using namespace std;

void readIn(Point &P) {
  char c;
  cin >> c >> P.x >> c
      >> P.y >> c;
}

void writeOut(Point &P){
  cout << '(' << P.x
       << ',' << P.y << ')'
       << endl;
}

Now, this Point class has a constructor and three member functions. Additionally, it has stand alone friend functions readIn and writeOut. In the following code we try unsuccessfully to call these stand alone functions:

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

int main(){
  Point P;
  readIn(P);
  writeOut(P);

  readIn(P.reflectX());  // clearly won't work, it's like "cin >> (a - b)"

  writeOut(P.reflectX()); // looks OK, but it isn't!  Compiler doesn't know
                          // that it's any different than the previous statement
  return 0;
}

When we try compiling this we get two error messages:

Error from call to readIn
ex1.cpp:41: error: no matching function for call to 'readIn(Point)'
/usr/include/unistd.h:425: note: candidates are: ssize_t readIn(int, void*, size_t)
ex1.cpp:20: note:                 void readIn(Point&)
Error from call to writeOut
ex1.cpp:43: error: no matching function for call to 'writeOut(Point)'
/usr/include/unistd.h:524: note: candidates are: ssize_t writeOut(int, const void*, size_t)
ex1.cpp:27: note:                 void writeOut(Point&)
    

This makes it look like the compiler cannot find the readIn and writeOut functions, but if you'll notice, the errors happen on the lines that take P.reflectX() as the argument. The compiler is telling you thatreadIn(Point&) and writeOut(Point&) are candidates. The compiler sees them, but won't let you pass the result of P.reflectX() as a reference. Hmmm... hold on to that thought.

Now, the call to readIn is clearly bad, since readIn will try to change the value of the object returned by the function call P.reflectX(). Well, this is an rvalue, it is an object that can appear on the right-hand side of an assignment, but not the left. It's like trying to x + 1 = a + b. It's not a variable! The result of a function call or an expression evaluation is called a temporary. The object exists temporarily until the expression in which it occurs is evaluated, and then it dies. C++ does not let you modify temporaries, so it does not let you use them as lvalues. lvalues are things that can appear on the left hand side of an assignment.

Going back to our errors, and thinking about temporaries, does it make sense why the compiler is giving us this error? readIn tries to modify the result of the function call, which is a temporary, and thus the expression is bad - it's an error. But, what about the call to writeOut? It isn't going to try to modify the temporary returned by the function call P.reflectX(), right? That's correct, but the compiler doesn't know it! You need to put the keyword const in front of the parameter type in writeOut's prototype and definition. This serves as a promise that the parameter won't get modified, and it's a promise the compiler believes when it compiles main.cpp, because it will hold you to it when compiling Point.cpp! Using const, we can fix the whole thing!

Point.h Point.cpp
class Point{
private:
  double x, y;

public:
  Point(double a = 0, double b = 0) {
    x = a; y = b;
  }
  double getX() { return x; }

  double getY() { return y; }

  Point reflectX()  {
    return Point(x,-y);
  }

  friend void readIn(Point &P);
  friend void writeOut(const Point &P);   //added const
};
#include "Point.h"
#include <iostream>
using namespace std;

void readIn(Point &P){
  char c;
  cin >> c >> P.x >> c
      >> P.y >> c;
}

void writeOut(const Point &P){  //added const
  cout << '(' << P.x
       << ',' << P.y << ')'
       << endl;
}
#include "Point.h"
#include <iostream>
using namespace std;

int main() {
  Point P;
  readIn(P);
  writeOut(P);

  writeOut(P.reflectX()); // Now everyone's happy.  True, "P.reflectX()" is a
                       // temporary and writeOut is pass-by-reference, but
                       // the parameter is declared const, which is a promise
                       // that writeOut won't modify it!
  return 0;
}
More const

You might argue that there's no reason for the writeOut function to be declared a friend of the class Point, since it could use the public member functions getX and getY instead of accessing the private data members x and y. I would say that sounds like a good point! Let's make the change:

Point.h Point.cpp
class Point{
private:
  double x, y;

public:
  Point(double a = 0, double b = 0) {
    x = a; y = b;
  }

  double getX() { return x; }

  double getY() { return y; }

  Point reflectX(){
    return Point(x,-y);
  }

  friend void readIn(Point &P);
};

void writeOut(const Point &P);
#include "Point.h"
#include <iostream>
using namespace std;

void readIn(Point &P){
  char c;
  cin >> c >> P.x >> c
      >> P.y >> c;
}

void writeOut(const Point &P){
  cout << '(' << P.getX()
       << ',' << P.getY() << ')'
       << endl;
}

Unfortunately, when we try to compile our main function with the new Point.h / Point.cpp files we get the following error message:

In function `void writeOut(const Point &)':
passing `const Point' as `this' argument of `double Point::getX()' discards qualifiers
passing `const Point' as `this' argument of `double Point::getY()' discards qualifiers

What does it mean? Well, first off you need to know that inside a member function you do not only have access to data members and functions, you also have access to a variable named this, which is simply a pointer to the object from which the member function was called. So if the following member function call is made: A.f(), inside the definition of f the variable this would point to A. On the other hand, if the same member function is called as B.f(), then in that call the variable this in f would point to B.

So, let's try to dissect the error message now. Inside the definition of writeOut, we have the parameter P of type const Point &. We make the call P.getX(), and the compiler complains that passing a const Point (i.e. P) as the this argument of getX() (i.e. as the object from which getX() is called) discards qualifiers (i.e. discards the const).

Bottom line: The compiler is complaining because it doesn't have any guarantee that the member functions getX() and getY() won't modify P in the calls to them! Since P is supposed to be const in writeOut, the compiler needs to be sure that P doesn't get modified.

Now, since we know that getX doesn't modify its "this " argument, i.e. the object from which it is called, we simply need to make the const promise to the compiler. The syntax for doing this is to put a constimmediately following the parenthesized argument list to the member function. In this case:

double getX() const { return x; }
double getY() const { return y; }

Now we have promised the compiler that we won't modify the "this " argument, i.e. we won't change P in the call P.getX(). Our code compiles just fine with this change. To finish this example off, let's note that the member function reflectX could also be defined as const, since we clearly do not expect a call like A.reflectX() to modify A.

Object creation - don't look behind that curtain!

A lot of object creation (and destruction) goes on "behind the scenes" in C++. The classic way to get a feeling for this is to write a program where the constructors and the destructor print out a message, so you can track when objects are created and destroyed. In the following program, see if you can determine how many Point objects are actually created:

main.cpp point.h point.cpp
#include "point.h"
using namespace std;

int main(){
  Point P, Q;
  readIn(P);
  readIn(Q);
  writeOut(midpoint(midpoint(P,Q),Q));

  return 0;
}
#include <iostream>
using namespace std;

class Point {
private:
  double x, y;
public:
  Point() {
    cout << "Default constructor!" << endl;
  }
  Point(int a, int b) {
    x = a;
    y = b;
    cout << "2-arg constructor!" << endl;
  }
  Point(const Point &P) {
    x = P.x;
    y = P.y;
    cout << "Copy constructor!" << endl;
  }
  ~Point();
  double getX() const { return x; }
  double getY() const { return y; }
  friend void readIn(Point &P);
};

void writeOut(Point P);
Point midpoint(Point A, Point B);
#include "point.h"

void readIn(Point &P){
  char c;

  cin >> c >> P.x >> c >> P.y >> c;
}

void writeOut(Point P){
  cout << '(' << P.getX()
       << ',' << P.getY()
       << ')' << endl;
}

Point::~Point(){
  cout << "Point dies!" << endl;
}

Point midpoint(Point A, Point B){
  return Point((A.getX() + B.getX())/2,
         (A.getY() + B.getY())/2);
}

It turns out that 7 different Point objects are created by this program:

Four of these seven are easy to see: Points P and Q are created with default constructors when the variables are defined, and the two calls to midpoint each create the point that they return with a call to the 2-argument constructor. Where do the three extra points come from? Well, notice that they are all created with the copy constructor. They come from the pass-by-value calls to midpoint and writeOut! All of this extra work can be avoided, however, by using pass by reference (with const) instead!

main.cpp point.h point.cpp
#include "point.h"
using namespace std;

int main(){
  Point P, Q;
  readIn(P);
  readIn(Q);
  writeOut(midpoint(midpoint(P,Q),Q));

  return 0;
}
#include <iostream>
using namespace std;

class Point{
private:
  double x, y;
public:
  Point() {
    cout << "Default constructor!" << endl;
  }

  Point(int a, int b) {
    x = a;
    y = b;
    cout << "2-arg constructor!" << endl;
  }

  Point(const Point &P) {
    x = P.x;
    y = P.y;
    cout << "Copy constructor!" << endl;
  }

  ~Point();
  double getX() const { return x; }
  double getY() const { return y; }
  friend void readIn(Point &P);
};

//changed below functions to pass by reference
//with const
void writeOut(const Point &P);
Point midpoint(const Point &A, const Point &B); 
#include "point.h"

void readIn(Point &P){
  char c;
  cin >> c >> P.x >> c >> P.y >> c;
}

//now pass by reference with const
void writeOut(const Point &P){
  cout << '(' << P.getX()
       << ',' << P.getY()
       << ')' << endl;
}

Point::~Point(){
  cout << "Point dies!" << endl;
}

//now pass by reference with const
Point midpoint(const Point &A, const Point &B){
  return Point((A.getX() + B.getX())/2,
         (A.getY() + B.getY())/2);
}

Now when we compile and run this version, those three "extra" points never get created:

The moral of this story? Pass-by-value is great for built-in types. For user defined types, however, it involves some overhead - all those calls to the copy constructor. Use pass-by-const-reference instead.

Temporary objects, copy constructors, and dynamic memory

Things really get ugly with temporary objects and copy constructors and all those other things when we have classes with data members. Here's an example illustrating why. Consider the following program:

main.cpp vect.h vect.cpp
#include "vect.h"

int main(){
  // Get dimension and create vector
  int n;
  cout << "how many components? ";
  cin >> n;
  Vect V(n);

  // Read component values
  cout << "Enter " << n << " values: ";
  for(int i = 0; i < n; i++)
    cin >> V.val[i];

  // Print component sum
  cout << sum(V) << " = ";
  cout << V.val[0];
  for(int i = 1; i < n; i++)
    cout << " + " << V.val[i];
  cout << endl;

  return 0;
}
#include <iostream>
using namespace std;

class Vect{
public:
  int dim;
  double *val;

  Vect(int n);
  ~Vect();
};

double sum(Vect A);
#include "vect.h"

Vect::Vect(int n){
  dim = n;
  val = new double[n];

  for(int i = 0; i < dim; i++)
    val[i] = 0;
}

Vect::~Vect() { delete []val; }

double sum(Vect A){
  double s = 0.0;

  for(int i = 0; i < A.dim; i++)
    s += A.val[i];

  return s;
}

Everything looks perfectly reasonable, doesn't it? But look what happens when you compile and run this program!

Not exactly what I expected! What happened to the val components of my Vect? Well, look at main(): the call sum(V) results in a copy of V being sent to sum, since we have pass-by-value. Since we have not definined our own copy constructor, the compiler supplies its own, which simply copies each data member individually. When the pointer V.val is copied to A.val, no new memory is allocated: A.val simply points to the same array. Now, when sum is finished, A's destructor gets called, and it deletes the array A.val. But this is the same array - physically the same array - as that pointed to by V.val. Thus, we have no guarantee that our array elements won't get replaced by random garbage, and indeed in this case that is precisely what happened.

There are two ways out of this problem: 1) simply avoid pass-by-value and use pass by reference instead, or 2) define a copy constructor that makes a deep copy of Vect, including allocating new arrays.

Rules for coping with temporary objects, pass by value, and copy constructors in the presence of dynamic data members

The easiest rule to making the headaches of this section go away is to simply abandon pass-by-value for user defined objects - or at least for objects with dynamic memory. By using pass by reference and const, the resulting code is every bit as natural as if copy constructors are properly implemented, and a lot more efficient. In fact, other languages, like Java, don't even have pass-by-value for user defined objects. One good idea is to define a copy constructor with an empty body, i.e. a copy constructor that does nothing, in the private section of your class definition. This actually disables pass-by-value for users of your class. That way they couldn't make this mistake if they wanted to.

The one thing you have to worry about, however, is correctly overloading an assignment operator for a class that has dynamically allocated data members. Using the above example:

Vect A(3);
...
...// some code here
...
if(true) {
  Vect B(3);
  B = A;
} // B goes out of scope and is destroyed, which also destroys A
  // because the assignment operator set B's pointer to the same
  // memory as A's pointer

You see, assignment has the same difficulty. You must either disable it by defining the operator as private:

private:
  Vect& operator=(const Vect &A) { }

or you must define it to work properly:

Vect& operator=(const Vect &A) {
  if (A != this) { // Sounds weird, but we want a statement like X = X to do nothing
    delete [] val;               // kill old array
    dim = A.dim;                 // set new dimension
    val = new double[A.dim];     // create new array
    for(int i = 0; i < dim; i++) // copy contents of A into new array
      val[i] = A.val[i];
  }
}

If you think about your financial simulator, you should remember that you never had any reason to assign, for example, one Bank object to equal another. Same for Expenses and Income objects. For many classes simply prohibiting pass-by-value and assignment make a lot of sense.