Chapter 3
Abstraction Mechanisms, Part 1

Kurt Nørmark
Department of Computer Science, Aalborg University


Abstract
Previous lecture Next lecture
Index References Contents
In this lecture we discuss the fundamental abstraction mechanisms in C++: classes and structs. At the end of the lecture, the concept of C++ friends is explored in relatively great details.

From C# classes to C++ classes
Slide Annotated slide Contents Index
References 

We introduce classes in C++ by studying similarities and differences relative to C# classes

Program: Class Point in C#.
// This is a C# program.

using System;

public class Point {
  private double x, y;

  public Point(double x, double y){
   this.x = x; this.y = y;
  }

  public double Getx (){
    return x;
  }

  public double Gety (){
    return y;
  }

  public void Move(double dx, double dy){
    x += dx; y += dy;
  }

  public double DistanceTo(Point p){
    return Math.Sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
  }

  public override string ToString(){
    return "Point: " + "(" + x + ", " + y + ")" + ". ";
  }
}

Program: Class Point i C++ together with a main function - member functions defined in class definition.
// Very simple C++ program, in transition from a C# version.

#include <cmath>
#include <iostream>

using namespace std;

class Point {
private: 
  double x, y;

public:

  Point(double x_coord, double y_coord): x(x_coord), y(y_coord)
    { }

  Point(): x(0.0), y(0.0)
    { }

  double getx (){
    return x;
  }

  double gety (){
    return y;
  }

  void move(double dx, double dy){
    x += dx; y += dy;
  }

  double distance_to(Point p){
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
  }

};



int main(){

  Point p, 
        q = Point(3.0, 4.0),
        r(3.0, 4.0);

  p.move(2,3);
  q.move(4,5);

  double d = q.distance_to(r);

  cout << d << endl;

  cout << "Point p: " << p.getx() << ", " << p.gety() << endl;
}

Program: Class Point i C++ together with a main function - member functions defined outside the class.
// Class definition and member definitions outside the class.

#include <cmath>
#include <iostream>

using namespace std;

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}



int main(){

  Point p, 
        q = Point(3.0, 4.0),
        r(3.0, 4.0);

  p.move(2,3);
  q.move(4,5);

  double d = q.distance_to(r);

  cout << d << endl;

  cout << "Point p: " << p.getx() << ", " << p.gety() << endl;
  cout << "Point q: " << q.getx() << ", " << q.gety() << endl;
}

Program: Class Point i C++ - with an output function for points.
// A version with non-member output function. An overloading of operator<<

#include <cmath>
#include <iostream>

using namespace std;

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

ostream& operator<<(ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}



int main(){

  Point p, 
        q = Point(3.0, 4.0),
        r(3.0, 4.0);

  p.move(2,3);
  q.move(4,5);

  double d = q.distance_to(r);

  cout << d << endl;

  cout << "Point p: " << p << endl;
  cout << "Point q: " << q << endl;
}

Organization of classes and members
Slide Annotated slide Contents Index
References 

Members do not need to be textually embedded in C++ classes and structs

Class design is emphasized by separating class definition from member definition

The programmer writes source code that reflects the class design

Program: The class design of class Point.
class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

In similar object-oriented languages the class desing is extracted by documentation tools from the full class definition

Classes, structs and namespaces
Slide Annotated slide Contents Index
References 

Structs and classes are similar in C++

A class/struct is a namespace "with some extra properties":

Reference
  • The C++ Programming Language: Page 234,849

  • Class

    • Defines a named type where members are private by default

    • Typically used for user-defined types

    • A namespace "with some extra properties":

  • Struct

    • Defines a named type where members are public by default

    • Typically used for aggregation of public data

    • A namespace "with some extra properties":

  • Namespace

    • A scope

    • Named or unnamed

    • Global

Reference

Program: Struct Point in C++ .
// struct Point is almost identical with class Point. Not necessarily a typical struct.

#include <cmath>
#include <iostream>

using namespace std;


struct Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point);
};

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p){
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

int main(){

  Point p, 
        q = Point(3.0, 4.0),
        r(3.0, 4.0);

  p.move(2,3);
  q.move(4,5);

  double d = q.distance_to(r);

  cout << d << endl;

  cout << "Point p: " << p.getx() << ", " << p.gety() << endl;
  cout << "Point q: " << q.getx() << ", " << q.gety() << endl;
}

Program: A namespace with struct Point in C++ .
// A namespace programmed in a similar way as struct Point.

#include <cmath>
#include <iostream>

using namespace std;

namespace PointNamespace {
  struct Point{
    double x, y;
  };

  double getx (Point);
  double gety (Point);
  void move(Point&, double, double);
  double distance_to(Point, Point);
};   

double PointNamespace::getx (Point p){
  return p.x;
}

double PointNamespace::gety (Point p){
  return p.y;
}

void PointNamespace::move(Point &p, double dx, double dy){
    p.x += dx; p.y += dy;
}

double PointNamespace::distance_to(Point p, Point other){
    return sqrt((p.x - other.x) * (p.x - other.x) + (p.y - other.y) * (p.y - other.y));
}

int main(){

  PointNamespace::Point
        p = {0,0}, 
        q = {3,4},
        r = {5,6};

  move(p,2,3);  // It is not necessary to qualify move with its namespace because
  move(q,4,5);  // move is looked up in the namaspace of its argument (stroustrup page 177).

  double d = distance_to(q,r);
  cout << d << endl;                                            // 3.60555

  cout << "Point p: " << getx(p) << ", " << gety(p) << endl;    // 2, 3
  cout << "Point q: " << getx(q) << ", " << gety(q) << endl;    // 7, 9
}

Program: Same with illustration of ambiguities relative to the global namespace.
// Illustrating ambiguities and the global namespace

#include <cmath>
#include <iostream>

using namespace std;

namespace PointNamespace {
  struct Point{
    double x, y;
  };

  double getx (Point);
  double gety (Point);
  void move(Point&, double, double);
  double distance_to(Point, Point);
};   

double PointNamespace::getx (Point p){
  return p.x;
}

double PointNamespace::gety (Point p){
  return p.y;
}

void PointNamespace::move(Point &p, double dx, double dy){
    p.x += dx; p.y += dy;
}   

double PointNamespace::distance_to(Point p, Point other){
    return sqrt((p.x - other.x) * (p.x - other.x) + (p.y - other.y) * (p.y - other.y));
}

// A funny move function.  Move in the global namespace  
void move(PointNamespace::Point &p, double dx, double dy){
  p.x += 2 * dx; p.y += 2 * dy;
}   

int main(){

  PointNamespace::Point
        p = {0,0}, 
        q = {3,4},
        r = {5,6};

  PointNamespace::move(p,2,3);      // call move in PointNamespace.
  PointNamespace::move(q,4,5);      // Now necessary to quality with the namespace.

  ::move(p,5,6);                    // call move in the global namespace.

  double d = distance_to(q,r);
  cout << d << endl;                // 3.60555

  cout << "Point p: " << getx(p) << ", " << gety(p) << endl;  // 12, 15
  cout << "Point q: " << getx(q) << ", " << gety(q) << endl;  // 7, 9
}

Functions outside classes
Slide Annotated slide Contents Index
References 

In contrast to similar object-oriented languages, C++ allows functions and variables outside classes

Helper functions are typically wrapped in a namespace together with one or more classes.

In the simple case they are placed in the global name space together with the classes

Reference
  • The C++ Programming Language: Page 240

Reference
  • Effective C++, Third edition: Item 23

  • Functions outside classes:

    • Which function should be defined outside classes - possible answers:

      • Functions that do not need to access the encapsulated state

      • Functions that we want to use without the dot operator

        • f(obj, ...) instead of obj.f(...)

        • Or instead of Class::f(...) where f is a static member

      • Other functions, such as the entry function main

  • Variables outside classes:

    • Global variables and variables in namespaces

Seen from the C perspective: funtions and variables outside classes represent backward compatibility

Compared with Java and C#: Less emphasis on static variables and methods

Constructors
Slide Annotated slide Contents Index
References 

The name of a constructor is the same as the name of its class or struct.

Constructors in C++ are in many ways similar to constructors in C# and Java

Reference
  • The C++ Programming Language: Page 226, 247, 270

  • Initialization of class data members with member initializers

    • Special syntax for initialization

    • Placed between the constructor header and the body

    • Passes arguments to the constructors of the members

    • Constructor chaining is not supported in C++

      • Instead, the constructors may delegate the initialization work to a (private) method

Program: The Point class.
class Point {
private: 
  double x, y;

public:
  Point();                    
  Point(double x);            
  Point(double x, double y);  

  // other methods
};

Program: Implementation of the Point class constructors.
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(): x(0.0), y(0.0){
}

Point::Point(double x_coord): x(x_coord), y(0.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Program: The Rectangle class.
#include "point.h"

class Rectangle{
private:
  Point upper_left, lower_right;

public:
  Rectangle();
  Rectangle(Point p1, Point p2);
  Rectangle(double x1, double y1, double x2, double y2);
 
  // other methods
};

Program: Implementation of the Rectangle class constructors.
#include <cmath>
#include <iostream>
#include "rect.h"

Rectangle::Rectangle(): upper_left(-1,1), lower_right(1,-1){
}

Rectangle::Rectangle(Point p1, Point p2): upper_left(p1), lower_right(p2){
}

Rectangle::Rectangle(double x1, double y1, double x2, double y2):
      upper_left(x1,y1), lower_right(x2,y2){
}

Program: Sample Rectangle constructions.
#include <iostream>
#include "rect.h"

using namespace std;

int main(){
  Point p1(1,2), p2(5,-3);
  Rectangle r1,                 // Use default constructor
            r2(1,2,5,-3),                   
            r3(p1,p2),
            r4(Point(1,2), Point(5,-3));

  // use of points and rectangles
}

Exercise 3.2. Methods in class Point and class Rectangle

In this exercise we will add methods to class Point and Rectangle from the enclosing slide.

First, however, we notice that a rectangle contains its two points. In a similar Java or C# program (programmed with classes) the rectangle would have references (in the meaning of C++ pointers) to two points.

Add a couple of methods that access the x and y coordinates of a point. This is very easy. Do you miss C#-like properties in C++?

Next, write two methods that access the two corner points of a rectangle. Do you want to return copies of the points? Pointers to the points? Or references to the points? Please play with each of these possiblities, and discuss pros and cons.

Add an area method to Rectangle. This method will most likely need to access to the x and y coordinates of the corner points of the rectangle.

Add move methods to both class Point and Rectangle. The methods should implement relative displacement of points and rectangles.

In Rectangle::move, are you able to establish (C++) references to its two corner points, such that moving the two points also will also move the rectangle?

If you get too frustrated by dealing with Point& you may consider a version that works on Point*...

Finally, supply output functions for Point and Rectangle objects, by overloading operator<< for these two classes.

Program: Illustration of legal and illegal member initializers - constructor chaining is not supported.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

Point::Point(double x_coord): Point(x_coord, 0.0) {   // Not allowed in C++.
                                                      // error: type  Point  is not a direct base of 'Point'
}


Point::Point(Point& p): this(p.x + 1.0, p.y + 2.0){   // Not allowed in C++.
}



double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Constructors - initialization versus assignment
Slide Annotated slide Contents Index
References 

The main task of a constuctor is to initalize data members

In C++ initialization and assignment must be distinguished

Initialization is controlled by copy constructors, assignment by operator= overloads

Reference
  • Effective C++, Third edition: Item 4

Program: The Rectangle class - header file.
#include "point.h"

class Rectangle{
private:
  Point upper_left, lower_right;

public:
  Rectangle();
  Rectangle(Point p1, Point p2);
  Rectangle(double x1, double y1, double x2, double y2);
 
  // other methods
};

Program: Rectangle constructors - with initialization.
#include <cmath>
#include <iostream>
#include "rect.h"

Rectangle::Rectangle(): upper_left(-1,1), lower_right(1,-1){
}

Rectangle::Rectangle(Point p1, Point p2): upper_left(p1), lower_right(p2){
}

Rectangle::Rectangle(double x1, double y1, double x2, double y2):
      upper_left(x1,y1), lower_right(x2,y2){
}

Program: Rectangle constructors - with default initialization and subsequent assignments.
#include <cmath>
#include <iostream>
#include "rect.h"

Rectangle::Rectangle(){
  upper_left = Point(-1,1);        // Assignments done after default initialization
  lower_right = Point(1,-1);       // Too expensive. Bad style in C++.
}

Rectangle::Rectangle(Point p1, Point p2){
  upper_left = p1;
  lower_right = p2;
}

Rectangle::Rectangle(double x1, double y1, double x2, double y2){
  upper_left = Point(x1,y1);
  lower_right = Point(x2,y2);
}

Const data members and references must be initialized - they cannot be assigned in the body of the constructor

Always prefer explicit initialization - the rules for implicit initialization are (too) complicated

More about constructors
Slide Annotated slide Contents Index
References 

Additional properties of constructos in C++

Reference
  • The C++ Programming Language: Page 226, 247, 270

  • Overloaded constructors

    • Constructors can be overloaded, just as other functions

  • Explicit constructors

    • By means of an explicit specifier before the constructor

    • Cannot be used in implicite type conversion

  • Default constructor

    • In case a constructor is defined there will be no parameterless default constructor

      • As in C#

    • Can be called without actual parameters

    • Either parameterless, or with default values for every parameter

    • Used for initialization of automatic variables of class type

  • Copy constructor

    • A constructor that takes a single reference parameter to its class

    • Generated automatically - if needed - and if not programmed explicitly

Use of constructors
Slide Annotated slide Contents Index
References 

We illustrate the use of overloaded constructors - both with 'static allocation' and 'dynamic allocation'

In order to distinguish the use of the individual constructors they carry out 'funny Point initializations'

  • Four funny Point constructors:

    • Point(): (0,7) - default constructor

    • Point(double d, double e): (d,e)

    • Point(double d): (d,20)

    • Point(Point& p): (p.x+1.0, p.y+2.0) - copy constructor

Program: Class Point with a variety of constructors.
class Point {
private: 
  double x, y;

public:
  Point();                    // default constructor:  (0,7)
  Point(double x);            // (x, 20)
  Point(double x, double y);  // (y, y)  
  Point(Point& p);            // copy constructor: (p.x+1, p.y+2) 

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Funny implementations of constructors.
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}


double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: Use of constructors - with automatic variables of class type.
//Point(): (0,7)        Point(double x): (x, 20)      Point(double x, double y): (y, y)       Point(Point& p) (p.x+1, p.y+2) 
//Reveal...

#include <iostream>
#include "point.h"

using namespace std;

int f(){
  Point p;
  cout << "Point p: " << p << endl;   // (0,7)
  
  Point r(11.0, 12.0);
  cout << "Point r: " << r << endl;   // (11,12)

  Point q;
  q = r;                              // default assignment - bit-wise copying, no use of copy constructor
  cout << "Point q: " << q << endl;   // (11,12)

  Point s(p);                         // The copy constructor used
  cout << "Point s: " << s << endl;   // (1,9)

  Point t(3.0);                       // Point(double) used
  cout << "Point t: " << t << endl;   // (3,20)

  Point u = t;                        // Another way to use the copy constructor, via an initializer
  cout << "Point u: " << u << endl;   // (4,22)


  Point ap[10];                       // The default constructor used 10 times
  for(int i = 0; i < 10; i++)         // (0,7) ten times
     cout << "ap[" << i << 
     "]: " << ap[i] << endl;  
}

int main(){
  f();
}

Program: Actual program output.
Point p: (0,7)
Point r: (11,12)
Point q: (11,12)
Point s: (1,9)
Point t: (3,20)
Point u: (4,22)
ap[0]: (0,7)
ap[1]: (0,7)
ap[2]: (0,7)
ap[3]: (0,7)
ap[4]: (0,7)
ap[5]: (0,7)
ap[6]: (0,7)
ap[7]: (0,7)
ap[8]: (0,7)
ap[9]: (0,7)

Program: An additional Point construction attempt - a trap.
#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         // The default constructor is used for both p and q  
        q1(),                         // Most likely an error. 
                                      // What is it? A declaration of a parameterless point returning function.
        r(11.0, 12.0),                // Point(double,double) used
        s(p),                         // The copy constructor used
        t(3.0),                       // Point(double) used
        u = t,                        // Another way to use the copy constructor, via an initializer
        ap[10];                       // The default constructor used 10 times

  q = r;                              // default assignment - bit-wise copying, no use of copy constructor

  cout << "Point p: " << p << endl;   // (0,7)
  cout << "Point q1: " << q1 << endl; // ?????
  cout << "Point r: " << r << endl;   // (11,12)
  cout << "Point s: " << s << endl;   // (1,9)
  cout << "Point t: " << t << endl;   // (3,20)
  cout << "Point u: " << u << endl;   // (4,22)

  for(int i = 0; i < 10; i++)         // (0,7) ten times
     cout << "ap[" << i << 
     "]: " << ap[i] << endl;  

}

int main(){
  f();
}

Exercise 3.3. Use of constructors with object on the free store

Rewrite the program shown above such that all Point objects are constructed in the free store (on the heap). The dynamically allocated points should have the same (x,y) coordinates as the points in the program shown above.

This involves use of pointer and dynamic allocation, instead of automatic variables of class type and 'static allocation'.

The version of the program produced in this exercise is - in some respect - similar to a Java or C# version of the program. Discuss!

Consider how to initialize the array ap, which in this version of the program should be an array of Point pointers.

Destructors
Slide Annotated slide Contents Index
References 

Destructors are essential for resource management purposes in C++

Most classes do not need destructors

Destructors are called implicitly

Reference
  • The C++ Programming Language: Page 242, 244.

  • Destructor activation:

    • When an automatic variable of class type goes out of scope

      • Because of normal termination

      • Because of exceptional termination - stack unwinding

    • When a dynamically allocated object is explicitly deleted from the free store (the heap)

      • With delete or delete[]

Program: Class Point with a destructor.
// Class Point with a destructor

class Point {
private: 
  double x, y;

public:
  Point();                 
  Point(double);    
  Point(double, double);    
  Point(Point&);           
  ~Point();                // Destructor

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Implementation of the destructor that reveal its activation.
// A 'revealing implementation' of the destructor.

#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}

Point::~Point(){
  std::cout << "Deleting point" << "(" << x << "," << y << ")" << std::endl;
  // No real work for the constructor in this version of class Point.
}


double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: Illustration of destructor activation when automatic variables go out of scope .
// When is the destructor called - and for which objects?  

#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         // The default constructor is used for both p and q  
        r(11.0, 12.0),                // Point(double,double) used
        s(p),                         // The copy constructor used
        t(3.0),                       // Point(double) used
        u = t,                        // Another way to use the copy constructor, via initializer
        ap[10],                       // The default constructor used 10 times
        *ps = new Point(1.0, 2.0);    // A dynamically allocated point accessed by a pointer.

  q = r;                              // default assignment - bit-wise copying, no use of copy constructor

  cout << "Point p: " << p << endl;   // (0,7)
  cout << "Point q: " << q << endl;   // (11,12)
  cout << "Point r: " << r << endl;   // (11,12)
  cout << "Point s: " << s << endl;   // (1,9)
  cout << "Point t: " << t << endl;   // (3,20)
  cout << "Point u: " << u << endl;   // (4,22)

  for(int i = 0; i < 10; i++)         // (0,7) ten times
     cout << "ap[" << i << 
     "]: " << ap[i] << endl;  

  cout << "*ps: " << *ps << endl;     // (1,2)

  // The Point destructor is called 16 times on exist from f.
  // *ps is NOT destructed. delete ps destructs it.
}

int main(){
  f();
}

Program: Actual program output.
Point p: (0,7)
Point q: (11,12)
Point r: (11,12)
Point s: (1,9)
Point t: (3,20)
Point u: (4,22)
ap[0]: (0,7)
ap[1]: (0,7)
ap[2]: (0,7)
ap[3]: (0,7)
ap[4]: (0,7)
ap[5]: (0,7)
ap[6]: (0,7)
ap[7]: (0,7)
ap[8]: (0,7)
ap[9]: (0,7)
Point *ps: (1,2)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(0,7)
Deleting point(4,22)
Deleting point(3,20)
Deleting point(1,9)
Deleting point(11,12)
Deleting point(11,12)
Deleting point(0,7)

A class that needs a destructor
Slide Annotated slide Contents Index
References 

An example where it is necessary to program a destructor

For sake of the example we introduce a dynamically allocated representation of points

Program: Class Point - with a pointer representation .
class Point {
private: 
  double *point_representation;

public:
  Point();                 
  Point(double, double);    
  ~Point();                // Destructor

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Implementation of class point - with an insufficient destructor .
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(){
  point_representation = new double[2];
  point_representation[0] = 0.0;
  point_representation[1] = 0.0;
}

Point::Point(double x_coord, double y_coord){
  point_representation = new double[2];
  point_representation[0] = x_coord;
  point_representation[1] = y_coord;
}

Point::~Point(){   // Does not free the point representation.
}

double Point::getx () const{
  return point_representation[0];
}

double Point::gety () const{
  return point_representation[1];
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A sample program with memory leaks.
#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         
        r(11.0, 12.0),
        ap[2],
        *s = new Point(13.0, 14.0);                

  cout << "Point p: "      << p << endl;     // (0,0)
  cout << "Point q: "      << q << endl;     // (0,0)
  cout << "Point r: "      << r << endl;     // (11,12)
  cout << "Point ap[0]: "  << ap[0] << endl;  // (0,0)
  cout << "Point ap[1]: "  << ap[1] << endl;  // (0,0)
  cout << "Point *s: "     << *s << endl;    // (13,14)

  // MEMORY LEAKS HERE:
  //   The Point destructor is called 5 times, for p, q, r, ap[0] and ap[1],
  //   but the calls fail to release the Point resources:
  //   The point representation of p, q, r, ap[0] and ap[1] are never deallocated.
  //   In addition: The point pointed to by s (together with its representation) is never deallocated:
  //   No destructor is called for s.
}

int main(){
  f();
}

Program: Class Point - with a pointer representation - same as before.
// Redoing the example - same header file as the previous version.

class Point {
private: 
  double *point_representation;

public:
  Point();                 
  Point(double, double);    
  ~Point();                // Destructor

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Implementation of class point - now with a destructor that deallocates the Point representation.
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(){
  point_representation = new double[2];
  point_representation[0] = 0.0;
  point_representation[1] = 0.0;
}

Point::Point(double x_coord, double y_coord){
  point_representation = new double[2];
  point_representation[0] = x_coord;
  point_representation[1] = y_coord;
}

Point::~Point(){
  std::cout << "Deleting point" << "(" << getx() << "," << gety() << ")" << std::endl;
  delete[] point_representation;
}


double Point::getx () const{
  return point_representation[0];
}

double Point::gety () const{
  return point_representation[1];
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A sample program - now without memory leaks.
#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         
        r(11.0, 12.0),
        ap[2],
        *s = new Point(13.0, 14.0);                


  cout << "Point p: "      << p << endl;      // (0,0)
  cout << "Point q: "      << q << endl;      // (0,0)
  cout << "Point r: "      << r << endl;      // (11,12)
  cout << "Point ap[0]: "  << ap[0] << endl;  // (0,0)
  cout << "Point ap[1]: "  << ap[1] << endl;  // (0,0)
  cout << "Point *s: "     << *s << endl;     // (13,14)

  delete s;

  // The Point destructor is called 6 times in total on exist from f.
  // No leaks any more.
  // 5 implicit calls for p, q, r, ap[0], and ap[1] respectively.
  // 1 call via delete s.
}

int main(){
  f();
}

Program: Output from the program.
Point p: (0,0)
Point q: (0,0)
Point r: (11,12)
Point ap[0]: (0,0)
Point ap[1]: (0,0)
Point *s: (13,14)
Deleting point(13,14)
Deleting point(0,0)
Deleting point(0,0)
Deleting point(11,12)
Deleting point(0,0)
Deleting point(0,0)

Exercise 3.4. Point destruction - now with a problematic point copy constructor

We continue our work with class Point, using a dynamically allocated point representation. It may be beneficial to review the two version of the classes (together with client programs) on the accompanying slide.

In the verison below we add a copy constructor to class Point. This constructor turns out to be problematic.

// Redoing the example - same header file as the previous version.

class Point {
private: 
  double *point_representation;

public:
  Point();                 
  Point(double, double);    
  Point(Point&);           // A new copy constructor
  ~Point();                // Destructor

  double getx () const;
  double gety () const;
  void move(double dx, double dy);
};

std::ostream& operator<<(std::ostream&, const Point&);

The class is implemented as follows:

#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(){
  point_representation = new double[2];
  point_representation[0] = 0.0;
  point_representation[1] = 0.0;
}

Point::Point(double x_coord, double y_coord){
  point_representation = new double[2];
  point_representation[0] = x_coord;
  point_representation[1] = y_coord;
}

Point::Point(Point& p): point_representation(p.point_representation){   // ATTEMPTING copy construction.
}

Point::~Point(){
  std::cout << "Deleting point" << "(" << getx() << "," << gety() << ")" << std::endl;
  delete[] point_representation;
}


double Point::getx () const{
  return point_representation[0];
}

double Point::gety () const{
  return point_representation[1];
}

void Point::move(double dx, double dy){
  point_representation[0] += dx;
  point_representation[1] += dy;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

There are problems in the following program: (1) When point r is moved, s is also moved. This is not as intended. In addition, the program aborts on exit from the function f.

#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         
        r(11.0, 12.0);

  cout << "Point p: "      << p << endl;         // (0,0)
  cout << "Point q: "      << q << endl;         // (0,0)
  cout << "Point r: "      << r << endl;         // (11,12)

  Point s(r);                                    // s is intended to be a copy of r.
  cout << "Point s: "      << s << endl << endl; // (11,12)

  r.move(1.0, 2.0);                              //  Moves r.

  cout << "Point s: "      << s << endl;         // (12,14)   !! s is ALSO MOVED - why?
  cout << "Point r: "      << r << endl << endl; // (12,14)

  // PROGRAM ABORTS ON EXIT FROM F - why?
}

int main(){
  f();
}

Explain both of the problems, and make a correct version of class Point.

If an object o allocates resources when constructed - or during its life time - o's destructor is responsible for relinquishing these resources when o is deleted or goes out of scope.

If o does not allocate such resources, its destructor is typcially not programmed

Resource acquisition is initialization - RAII
Slide Annotated slide Contents Index
References 

No automatic memory managment in C++

Therefore it is attractive to attach resource management to construction and destruction of stack-allocated objects

The resource is encapsulated in a class together with allocation and release member functions

Reference
  • The C++ Programming Language: Page 364-367

Program: A class Resource and its application in the function use_resource - principles only.
// Incomplete program that illustrates the RAII idea. Does not compile.

class Resource{
private:
  resource_type r;
  resource_id_t rti;
  
public:
  Resource(resource_id_t id): r(allocate_resource(id)), rti(id){
  }

  ~Resource() {
    release_resource(rti);
  }
  
private:
  resource_type allocate_resource(resource_id_t id){
    return ...;
  }

  void release_resource(resource_id_t id){
    ...
  }
};

void use_resource(resource_id_t r){
  Resource res(r);         // The constructor allocates the resource

  // ...

  // When the functions ends, or if an exception occurs before that,
  // the Resource destructor will be activated hereby relinquising it.

};

int main(){

  use_resource(actual_resource_id);

}

Program: A class Resource and its application in the function use_resource - compilable version.
// A complete version that can be used to illustrate what happens in case of exceptions after resource allocation. 
// This version reveals the resource allocation and deallocation on standard output. 

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

typedef string resource_id_t;
typedef int resource_type;

class Problem{};

class Resource{
private:
  resource_type r;
  resource_id_t rti;
  
public:
  Resource(resource_id_t id): r(allocate_resource(id)), rti(id){
  }

  ~Resource() {
    release_resource(rti);
  }
  
private:
  resource_type allocate_resource(resource_id_t id){
    cout << "Allocate resource: " << id << endl;
    return 1;
  }

  void release_resource(resource_id_t id){
    r = 0;
    cout << flush << "Release resource: " << id << flush << endl;
  }
};

void use_resource(resource_id_t r, bool problem_condition){
  Resource res(r);         // The constructor allocates the resource

  cout << "Use Resource" << endl;

  if (problem_condition){
    cout << "An exception is thrown" << endl;
    throw Problem();
  }

  // When the functions ends, or if an exception occurs before that,
  // the Resource destructor will be activated hereby relinquising it.
};

int main(int argc, char *argv[]){
  bool problem_condition = (argc >= 2) && (std::strcmp(argv[1],"problem") == 0);

  if (problem_condition) 
     cout << "The program will throw and exception during use of resource" << endl;
  else
     cout << "The program will use resource without throwing an exception" << endl;

  try{
    use_resource("sample_resource", problem_condition);
  }
  catch(Problem){
    cout << "Recovering" << endl;
  }

}

Program: Program output - with no problem_condition.
The program will throw and exception during use of resource
Allocate resource: sample_resource
Use Resource
An exception is thrown
Release resource: sample_resource
Recovering

Program: Program output - with a problem_condition.
The program will use resource without throwing an exception
Allocate resource: sample_resource
Use Resource
Release resource: sample_resource

Auto Pointers
Slide Annotated slide Contents Index
References 

An auto pointer, auto_ptr, is an encapsulation of a pointer that uses the RAII resource management idea

Reference
  • The C++ Programming Language: Page 367

Reference

  • An auto pointer uses destructive copy semantics

    • An assignment of auto pointers, lhs = rhs, modifies the lhs as well as the rhs

    • rhs becomes NULL

  • The get method in auto_ptr returns the encapsulated pointer

Program: The usual class Point - nothing of particular interest.
// Just the usual Point class. Nothing of particular interest.

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: An illustration of auto_ptr<Point>.
#include <memory>
#include <iostream>
#include <string>
#include "point.h"

using namespace std;

void f(Point *pp1){
  auto_ptr<Point>ap1(pp1),
                 ap2(new Point(3,4));

  // Use auto_ptr<Point> like Point*
  cout << "ap1: " << *ap1 << endl;   // (1,2)
  cout << "ap2: " << *ap2 << endl;   // (3,4)

  ap2->move(1,1);
  cout << "ap1-ap2 dist: " << ap1->distance_to(*ap2) << endl;  // 4.24264

  // Destructive copying:  ap2 is deleted
  //                       ap2 becomes a pointer to ap1
  //                       ap1 is unbound (corresponding to NULL).
  ap2 = ap1;

  if (ap1.get())                        // ap1 is NULL      !!!
     cout << "ap1: " << *ap1 << endl;  
  else
     cout << "ap1 is NULL" << endl;

  if (ap2.get())                        // (1,2)
     cout << "ap2: " << *ap2 << endl;
  else
     cout << "ap2 is NULL" << endl;

  // ap1 and ap2 are deleted on exit from f
  // ap2 refers to pp1, which therefore is deleted.
}

int main(){
  Point *p = new Point(1,2);
  f(p);

  cout << "p: " << *p << endl; // Unsafe! 
                               // p has been deleted by f.
}

Program: The program output.
ap1: (1,2)
ap2: (3,4)
ap1-ap2 dist: 4.24264
ap1 is NULL
ap2: (1,2)
p: (7.42291e+159,2)

An auto pointer 'owns the pointer' - no other auto pointer should exist to the object

Due to the special meaning of copying, auto pointers cannot be used as the element type in standard containers

Object copying
Slide Annotated slide Contents Index
References 

It is possible to copy objects as part of initializations and in assignments

Plain and simple value semantics is used for objects of class types in C++

Reference
  • The C++ Programming Language: Page 229

The copy constructor and overloading of the assignment operator can be used to redefine the default meaning of object copying

Copying Point objects in parameter passing
Slide Annotated slide Contents Index
References 

We write a program that passes Point objects in and out of trivial identify functions

We want to illustrate what happens when objects are passed in and out of functions

Funny constructors reveal what happens

Program: Class Point with a variety of constructors.
class Point {
private: 
  double x, y;

public:
  Point();                    // default constructor:  (0,7)
  Point(double x);            // (x, 20)
  Point(double x, double y);  // (y, y)  
  Point(Point& p);            // copy constructor: (p.x+1, p.y+2) 

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Funny implementations of constructors.
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}


double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: Passing Point objects in and out of functions.
// Point(): (0,7)    Point(x): (x, 20)    Point(x,y): (x,y)    Point(p) = (p.x+1,p.y+2)

#include <iostream>
#include "point.h"

using namespace std;

Point g(Point p){
  return p;
}  

Point& h(Point& p){
  return p;
}  

Point i(Point& p){
  return p;
}  

void f(){
  Point o(21.0, 22.0),            // (21,22)
        p, q, r;                  // all (0,7)

  p = g(o);                       // o is (21,22). Passed in by value as (22, 24).
                                  // g copies the object back via return, 
                                  // using the copy constructor:  (23, 26).

  q = h(o);                       // o is (21,22). Passed by reference as (21,22).
                                  // the object is passed back by reference, still (21, 22).

  r = i(o);                       // o = (21, 22). Passed by reference: (21,22).
                                  // i returns the point, copied by  
                                  // use of copy constructor: (22, 24).

  cout << "Point p: " << p << endl;  // (23,26)
  cout << "Point q: " << q << endl;  // (21,22)
  cout << "Point r: " << r << endl;  // (22,24)
}

int main(){
  f();
}

Program: Actual program output.
Point p: (23,26)
Point q: (21,22)
Point r: (22,24)

Example of copying objects: Default copying
Slide Annotated slide Contents Index
References 

Motivates the need for copy constructors and assignment overloading in C++

The starting point is automatic variables of class type (value semantics)

Class LineSegment has a pointer to the first Point in an array

Reference
  • The C++ Programming Language: Page 245-246

Program: Class LineSegment WITHOUT copy constructor and and WITHOUT assignment operator. Header file.
#include <iostream>

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

class LineSegment {
private: 
  Point *points;
  size_t number_of_points;
public:
  LineSegment(size_t s = 10);   // Serves the role as default constructor.
  ~LineSegment();
  // ...
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: The corresponding cc file - not particularly relevant.
#include <cmath>
#include <cstddef>
#include <iostream>
#include "point-lineseg.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
  x += dx; y += dy;
}

double Point::distance_to(Point p) const{
  return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}


LineSegment::LineSegment(size_t s){
    points = new Point[number_of_points = s];
    std::cout << "LineSegment Constructor" << std::endl;
  }

LineSegment::~LineSegment(){
    delete[]points;
    std::cout << "LineSegment Destructor" << std::flush << std::endl;
  }


std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A function that constructs, initializes and assigns LineSegments.
#include <iostream>
#include "point-lineseg.h"

using namespace std;

void h(){
  LineSegment ls1,       // LineSegment(10) constructed
              ls2 = ls1, // copy initialization
              ls3;       // LineSegment(10) constructed
                         

  ls3 = ls2;             // copy assignment - ls3 is now a copy of ls2.

  // Here all LineSegments ls1, ls2 and ls3  refer to the same Point array
  // because the Point pointers are just copied in the
  // initialization and the assignment shown above.

  // Destructors called for ls1, ls2, ls3 on exit from h.
  // Trouble for the two last destructions.
}

int main(){
  h();
}

Program: Program execution.
LineSegment Constructor
LineSegment Constructor
LineSegment Destructor
Aborted (core dumped)

The problem is that the first destruction of a LineSegment object destructs (deallocates) the shared Point array in the free store

The two next destructions encounter dangling references

The program is improved on the next slide

Example of copying objects: Programmed copying
Slide Annotated slide Contents Index
References 

We want to modify the meaning of copying during initialization and assignment

The point array of the line segment is copied by the copy constructor and by the assignment operator

The following versions of the program illustrates a typical pattern for copy constructors and assignment operators

Reference
  • The C++ Programming Language: Page 245-246

Program: Class LineSegment WITH a copy constructor and and WITH an assignment operator. Header file.
#include <iostream>

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

class LineSegment {
private: 
  Point *points;
  size_t number_of_points;
public:
  LineSegment(size_t = 10);                     // Default constructor
  LineSegment(const LineSegment&);              // Copy constructor
  ~LineSegment();                               // Destructor

  LineSegment& operator=(const LineSegment&);   // Assignment operator
  // ...
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: The corresponding cc file with implementations of the copy constructor and the assignment operator.
#include <cmath>
#include <cstddef>
#include <iostream>
#include "point-lineseg.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
  x += dx; y += dy;
}

double Point::distance_to(Point p) const{
  return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}


LineSegment::LineSegment(size_t s){                             // Default constructor
  points = new Point[number_of_points = s];
  std::cout << "LineSegment default constructor" << std::endl;
}

LineSegment::LineSegment(const LineSegment& ls){                // Copy constructor
  points = new Point[number_of_points = ls.number_of_points];
  for(int i = 0; i < number_of_points; i++) points[i] = ls.points[i];
  std::cout << "LineSegment copy constructor" << std::endl;
}  

LineSegment::~LineSegment(){                                    // Destructor
  delete[]points;
  std::cout << "LineSegment Destructor" << std::endl;
}  


LineSegment& LineSegment::operator= (const LineSegment& ls){    // Assignment operator
  if (this != &ls){  // not self-assignment;
    delete[]points;
    points = new Point[number_of_points = ls.number_of_points];
    for(int i = 0; i < number_of_points; i++) points[i] = ls.points[i];
  }
  std::cout << "LineSegment assignment" << std::endl;
  return *this;
}   


std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A function that constructs, initializes and assigns LineSegments.
#include <iostream>
#include "point-lineseg.h"

using namespace std;

void h(){
  LineSegment ls1,       // LineSegment(10) constructed
              ls2 = ls1, // copy initialization
              ls3;       // LineSegment(10) constructed
                         

  ls3 = ls2;             // copy assignment

  // All LineSegments refer to different Point arrays.

  // Destructors called for ls1, ls2, ls3: 
  // Now OK.
}

int main(){
  h();
}

Program: Program execution.
LineSegment default constructor
LineSegment copy constructor
LineSegment default constructor
LineSegment assignment
LineSegment Destructor
LineSegment Destructor
LineSegment Destructor

Reference
  • Effective C++, Third edition: Item 10

The copy constructor makes a copy of of the point array

The assignment deallocates the point array of the lhs (which is overwritten) and reallocates space for the points in the new copy

The programs above follow the pattern from Stroustrup §10.4.4.1 page 245

Preventing object copying
Slide Annotated slide Contents Index
References 

If you do not define a copy constructor and an assignment operator yourself, the compiler provides them for you

This is not convenient if you explicitly want to prevent object copying

Compiler enforced prevention

Reference
  • Effective C++, Third edition: Item 6

  • Solution

    • Declare the copy constructor and the assignment operator as private

    • Do not define these

    • Leads to linker errors in case the copy constructor or the assignment is actually used...

    • See Effective C++, Third edition item 6

This is a trick!

Classes and Conversion
Slide Annotated slide Contents Index
References 

Given a user defined type T, programmed via a class or struct:

It is possible to convert from some type S to type T via constructors in T

It is possible to convert from type T to some type S via conversion operators in T

Item 5 of More Effective C++ is informative about implicit type conversions

Reference

  • In which cases are conversion operators necessary? (Why not use a constructor in S of parameter T)

    • When converting to built in types

    • We cannot program constructors (with a T parameter) in a built-in type S

  • Conversion operator syntax - such as conversion to type int from class T

    • T::operator int () const {...}

    • Not:     int T::operator int () const {...}

    • Thus, constructor-like syntax

Program: istream conversion to bool.
  while (cin >> i)   // convert istream to bool or int
     cout << i*2 << " " << endl;

Program: The while loop in the context of a full C++ program.
#include <iostream>
#include <string>

using namespace std;

int main(){
  int i;
  while (cin >> i)   // convert istream to bool or int
     cout << i*2 << " " << endl;
  cout << "DONE";
}

Implicit Conversion
Slide Annotated slide Contents Index
References 

Conversion operators TT::operator S() and constructors T::T(S) can be called implicitly

  • Implicit conversions

    • A constructor can be used implicitly, unless it is marked explicit.

    • A conversion operator can be activated implicitly.

  • Too many implicit conversions may easily lead to ambiguities

    • The compiler finds out

    • The programmer will have to resolve such issues.

  • An alternative to conversion operators and implicit use

    • Program an ordinary member function instead of a conversion operator

    • T::make_int(){...}       instead of

    • T::operator int(){...}

Classes and Conversion: Examples
Slide Annotated slide Contents Index
References 

Illustrates conversion between a built-in type and a user defined type: double and Point

Program: Class Point with conversion constructor and conversion operator, from and to double.
class Point {
private: 
  double x, y;

public:
  Point();                      // (0,7)
  Point(double d);              // constructor from double:  (d, 20)
  Point(double d, double e);    // (d, e)
  Point(Point& p);              // (p.x+1, p.y+2) 

  operator double() const;      // conversion operator to double: x+y

  double getx () const;
  double gety () const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Class Point implementation.
#include <cmath>
#include <iostream>
#include "point1.h"


Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}


double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

Point::operator double() const {
  return x + y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: Use implicit of the conversions.
// Illustration of implicit conversions between Point and double.
// Point(): (0,7)   Point(x): (x, 20)   Point(x,y): (x,y)    Point(p) = (p.x+1,p.y+2)    double(p) = p.x+p.y

#include <iostream>
#include <string>
#include "point1.h"

using namespace std;

int main(){
  double d1, d2;
  Point p1, p2;      // Both (0, 7), not important for 
                     // this example.

  p1 = 5.0;          // (5, 20)  - Using Point(double)
  p2 = Point(p1);    // (6, 22)  - Using copy constructor. Default assignment is used.

  cout << "p1: " << p1 << endl;    // (5, 20)
  cout << "p2: " << p2 << endl;    // (6, 22)

  d1 = p1;           // 25  - Using conversion operator double() in Point.
  d2 = p1 - p2;      // Implicitly converts both p1 and p2 to doubles: 25 and 28.
                     // Using conversion operator twice.
                     // 25 - 28 == -3, 

  cout << "d1: " << d1 << endl;  // 25
  cout << "d2: " << d2 << endl;  // -3
}

Program: Program output.
p1: (5,20)
p2: (6,22)
d1: 25
d2: -3

The following illustrates conversion between user-defined types: Point and Tripple

Program: Class Tripple with Tripple(Point) constructor and a Point conversion operator.
// New example: Converting between Point an Tripple...

class Point {
private: 
  double x, y;

public:
  Point();                      // (0,7)
  Point(double d);              // constructor from double:  (d, 20)
  Point(double d, double e);    // (d, e)
  Point(Point& p);              // (p.x+1, p.y+2) 

  operator double() const;      // conversion operator to double: x+y

  double getx () const;
  double gety () const;
};

class Tripple {
private:
  int a, b, c;

public:
  Tripple();                    // (0, 0, 0)
  Tripple(int x, int y, int z); // (x, y, z)
  Tripple(Point p);             // (p.x, p.y, 0)
  operator Point() const;       // conversion operator to Point: (a, b)

  friend std::ostream& operator<<(std::ostream&, const Tripple&);
};


std::ostream& operator<<(std::ostream&, const Point&);
std::ostream& operator<<(std::ostream&, const Tripple&);

Program: Class Tripple implementation.
#include <cmath>
#include <iostream>
#include "point1.h"

// Tripple:

Tripple::Tripple(): a(0), b(0), c(0){
}

Tripple::Tripple(int x, int y, int z): a(x), b(y), c(z){
}

Tripple::Tripple(Point p): a(int(p.getx())), b(int(p.gety())), c(0){
}

Tripple::operator Point() const {
  return Point(a, b);
}

std::ostream& operator<<(std::ostream& s, const Tripple& tr){
  return s << "[" << tr.a << "," << tr.b << "," << tr.c << "]";
}




// Point:

Point::Point(): x(0.0), y(7.0){
}

Point::Point(double x_coord): x(x_coord), y(20.0){
}

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(Point& p): x(p.x + 1.0), y(p.y + 2.0){
}


double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

Point::operator double() const {
  return x + y;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: Illustration of conversions.
#include <iostream>
#include <string>
#include "point1.h"

using namespace std;

int main(){
  Point p1, p2;      // Both (0, 7)
  Tripple t1;        // (0, 0, 0)

  cout << "t1: " << t1 << endl;   // [0, 0, 0]

  t1 = p2;           // Tripple constructor on Point is used.
                     // p2 = (0,7) is copied into the constructor, using Point copy constructor: (1,9)
                     // This point is passed into the Tripple(Point) constructor. t1 becomes (1,9,0).

  p1 = t1;           // Tripple t1 = (1,9,0) converted to Point using conversion operator Point in
                     // class Tripple: (1, 9).
                     // This point is - surprise - converted to double via the double conversion operator
                     // in class Point: 10
                     // This double is converted to a Point using Point(double) constructor
                     // (10, 20). 
                     // In summary: [1,9,0] -> (1,9) -> 10 -> (10,20).

  cout << "t1: " << t1 << endl;  // [1, 9, 0]
  cout << "p1: " << p1 << endl;  // (10, 20)
}

Program: Program output.
t1: [0,0,0]
t1: [1,9,0]
p1: (10,20)

Exercise 3.5. Conversion via constructors

In the program shown above, a Point can be converted to a Tripple via a constructor Tripple(Point). The other way around, we convert a Tripple to a Point with use of a conversion operator in class Tripple

In this exercise we want to provide for a more symmetric solution. We keep the constructor Tripple(Point). Your task is to program the symmetric constructor Point(Tripple). Your starting point is the header file and the cpp file

You may enconter a couple of minor challenges when you rewrite the program. First, class Tripple must now be declared (it must be known by the compiler) in order to compile class Point. The reason is that class Point now uses the name Tripple (in the new constructor).

As another challenge, the new constructor Point(Tripple) in class Point needs access to the three encapsulated, private data members in class Tripple. Consider setting up appropriate friendship, or program accessor member functions.

With this in place, rerun the program from above. Do you get the expected results (without surprises)?

Static class members
Slide Annotated slide Contents Index
References 

Static class members are related to the class as such

Static class members in C++ are similiar to static variables and methods in Java and C#

Reference
  • The C++ Programming Language: Page 228

Program: A variant of class Point with static members.
class Point {
private: 
  double x, y;
  static Point defaultPoint;        // Cannot be initialized here. 

public:
  Point(double, double);
  Point();                            // Default constructor relies on defaultPoint
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const; 
  static Point origo();
  static void setDefaultPoint(Point);
  static void setDefaultPoint(double, double);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Implementation of class Point.
#include <cmath>
#include <iostream>
#include "point.h"

Point Point::defaultPoint = Point(5,7);  // Necessary. Linking problems if it is omitted.
                                         // Serves as initialization of the static variable member.

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(){                          // The default constructor uses the the static 
  x = defaultPoint.x;                    // data member defaultPoint.
  y = defaultPoint.y;
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

Point Point::origo(){
  return Point(0,0);
}

void Point::setDefaultPoint(Point p){
  defaultPoint.x = p.x;
  defaultPoint.y = p.y;
}

void Point::setDefaultPoint(double xcord, double ycord){
  defaultPoint.x = xcord;
  defaultPoint.y = ycord;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A client of class Point.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p,                     // Default constructor use defaultPoint: (5,7)
        q(3,4),                // (3,4)
        r = Point::origo(),    // origo can be accessed via the class: Point::origo().
        s = p.origo();         // ... and via an instances: p.origo(). Both are (0,0).
  
  cout << "p: " << p << endl;  // (5,7)
  cout << "q: " << q << endl;  // (3,4)
  cout << "r: " << r << endl;  // (0,0)
  cout << "s: " << s << endl;  // (0,0)

  Point::setDefaultPoint(q);

  Point t;                     // The default constructor uses 
                               // the new defaultPoint.
  cout << "t: " << t << endl;  // (3,4)
}

Program: Program output.
p: (5,7)
q: (3,4)
r: (0,0)
s: (0,0)
t: (3,4)

Initialization of non-local static objects is problematic in general (in particular initialization order).

Therefore we explore an alternative.

Reference
  • Effective C++, Third edition: Item 4, and 18 (page 80)

Program: A variant of class Point with static member function for the defaultPoint.
class Point {
private: 
  double x, y;
                                      // No static variable in this version
public:
  Point(double, double);
  Point();                            // Default constructor relies on defaultPoint()
  static Point& defaultPoint();       // The default point is a static member function
  double getx () const;               // ... that encapsulate a local static object 
  double gety () const;               // ... cf. Effective C++ item 4.
  void move(double, double);
  double distance_to(Point) const; 
  static Point origo();
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Implementation of class Point.
#include <cmath>
#include <iostream>
#include "point.h"


Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(defaultPoint().x), y(defaultPoint().y) { 
}

double Point::getx () const{
  return x;
}

Point& Point::defaultPoint(){    // Returns a reference to 
  static Point pt(5,7);          // local static variable
  return pt;
}
  

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

Point Point::origo(){
  return Point(0,0);
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A client of class Point.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p,                         // Default constructor use defaultPoint(): (5,7)
        q(3,4);                    // (3,4)
  
  cout << "p: " << p << endl;      // (5,7)
  cout << "q: " << q << endl;      // (3,4)

  Point::defaultPoint().move(1,1); // Move the default point relatively ...

  Point t;                         // The default constructor uses 
                                   // the moved defaultPoint.
  cout << "t: " << t << endl;      // (6,8)  
}

Const member functions
Slide Annotated slide Contents Index
References 

It is possible to mark a member function as constant

A constant member function is not allowed to change the state of the object.

In addition, it makes sense to call a constant member function on a constant object

Reference
  • The C++ Programming Language: Page 229

Reference
  • Effective C++, Third edition: Item 3

Program: A variant of Point with cached polar representation.
class Point {
private: 
  bool is_polar_cached;
  double r, a;                           // Cached polar representation of point.

  double x, y;
  void do_polar_caching() const;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  double getr () const;
  double geta () const;
  void move(double, double);
  double distance_to(Point) const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: The implementation of class Point - with compilation problems.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord),
                                              is_polar_cached(false){
}

Point::Point(): x(0.0), y(0.0), 
                is_polar_cached(false){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

double Point::getr () const {
  if (is_polar_cached)
    return r;
  else{
    do_polar_caching();
    return r;
  }
}

double Point::geta () const {
  if (is_polar_cached)
    return a;
  else{
    do_polar_caching();
    return a;
  }
}

void Point::do_polar_caching() const{  // Must be const, when called from another const method.
  std::cout << "Caching" << std::endl;
  r = sqrt(x*x + y*y);      // error: assignment of data-member  Point::r  in read-only structure
  a = atan2(y,y);           // error: assignment of data-member  Point::a  in read-only structure
  is_polar_cached = true;   // error: assignment of data-member  Point::is_polar_cached  in read-only structure
}

void Point::move(double dx, double dy){
    is_polar_cached = false;
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

The problem is that const member functions are not allowed to modify the caching variables.

In the next version - on the following page - we show how to deal with this problem

Const member functions - const and mutable
Slide Annotated slide Contents Index
References 

A member function may be logically constant, but the underlying state of the object may be physically non-constant (mutable)

It is possible to mark a data members as mutable

Mutable members can always be updated, even if they belong to a const object

Reference
  • The C++ Programming Language: Page 232

Program: A variant of Point with cached polar representation.
class Point {
private: 
  mutable bool is_polar_cached;
  mutable double r, a;          // Cached polar representation of point.

  double x, y;
  void do_polar_caching() const;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  double getr () const;
  double geta () const;
  void move(double, double);
  double distance_to(Point) const;
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: The implementation of class Point - with compilation problems.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord),
                                              is_polar_cached(false){
}

Point::Point(): x(0.0), y(0.0), 
                is_polar_cached(false){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

double Point::getr () const {
  if (is_polar_cached)
    return r;
  else{
    do_polar_caching();
    return r;
  }
}

double Point::geta () const {
  if (is_polar_cached)
    return a;
  else{
    do_polar_caching();
    return a;
  }
}

void Point::do_polar_caching() const{  // Must be const, when called from another const method.
  std::cout << "Caching" << std::endl;
  r = sqrt(x*x + y*y);      // Now OK to update mutable data members
  a = atan2(y,y);            
  is_polar_cached = true;    
}

void Point::move(double dx, double dy){
    is_polar_cached = false;
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A simple client program of Point.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p(1,1);

  double pa = p.geta(),    // Activates the caching.
         pr = p.getr();    

  cout << "Point p rectangular: " << p.getx() << ", " << p.gety() << endl;  
  cout << "Point p polar: " << p.getr() << ", " << p.geta() << endl;        
                                                                            
  p.move(2,2);             // Invalidates the cache
  cout << "p moved relatively by (2,2)" << endl;

  cout << "Point p rectangular: " << p.getx() << ", " << p.gety() << endl;  
  cout << "Point p polar: " << p.getr() << ", " << p.geta() << endl;         // Activates the caching.
}

Program: Program output.
Caching
Point p rectangular: 1, 1
Point p polar: 1.41421, 0.785398
p moved relatively by (2,2)
Point p rectangular: 3, 3
Caching
Point p polar: 4.24264, 0.785398

The problem is that const member functions are not allowed to modify the caching variables.

In the next version - on the following page - we show how to deal with this problem

Object Self-reference
Slide Annotated slide Contents Index
References 

In non-static member functions, this is a pointer to the object on which the function has been invoked

Reference
  • The C++ Programming Language: Page 230

Program: Class Point - almost the usual header file.
class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  Point* move(double, double);    // move returns this
  double distance_to(Point);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Class Point - a variant with explicit use of this.
// A variant of point.cc where we use this more than usual.

#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return this->x;
}

double Point::gety () const{
  return this->y;
}

Point* Point::move(double dx, double dy){
  this->x += dx; this->y += dy;
  return this;  
}

double Point::distance_to(Point p){
  return sqrt((this->x - p.x) * (this->x - p.x) +
              (this->y - p.y) * (this->y - p.y));
}


std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A sample use of points, with emphasis on moving.
#include <iostream>
#include <string>
#include "point.h"

int main(){
  using namespace std;

  Point p1(1,2),
        *p2 = new Point(1,2);

  p1.move(1,1)->move(2,2);
  p2->move(1,1)->move(2,2);

  cout << p1 << endl;    // (4,5)
  cout << *p2 << endl;   // (4,5)
}

The use of this in C++ is similar to the use of this in both C# and Java

Inline member functions
Slide Annotated slide Contents Index
References 

In C++ the programmer can ask for inlining of functions

Reference
  • The C++ Programming Language: Page 235, 144, 199

  • Inline functions

    • An efficiency concern

    • A plea to the compiler - not a requirement

    • Inline functions must be defined in every translation unit where they are used

      • Inline functions can be defined in header files

    • Some functions cannot easily be inlined

      • Recursive functions

    • Member functions defined inside class definitions are implicitly marked as inlined

Concrete classes
Slide Annotated slide Contents Index
References 

Bjarne Stroustrup distinguishes between concrete classes and abstract classes

Reference
  • The C++ Programming Language: Page 236-242

  • Concrete classes

    • Value types, as programmed with structs in C#

    • Similar to built in types

    • No superclasses - no subclasses

  • Non-concrete classes

    • Classes with pure virtual member functions

    • Classes in class hierarchies

    • Object-oriented programming

The class Date in §10.3 in The C++ Programming Language is an example of a concrete class

Visibility and Access Control
Slide Annotated slide Contents Index
References 

C++ supports the well-known distictions of private, protected and public members

  • Access control in C++

    • Visibility is associated with regions of declarations in the class, not the individual members

    • Classes and structs have different default visibility rules

    • Access rights to private members can be delegated to other parts of a C++ program by use of friends

C++ is similar to most other object-oriented programming languages with respect handling of visibility

With one notable exception, friends, which will be discussed next

Friends
Slide Annotated slide Contents Index
References 

A friend of class C has access to private and protected members of C

Reference
  • The C++ Programming Language: Page 278-282

  • Friendship principles:

    • Friendship is provided by the class that encapsulate members with access limitations

      • Friendship cannot be taken by a class or function that wants access

    • Friendship can be granted to

      • an ordinary function, or to an overloaded operator

      • a member function in another class

      • all members of another class

    • Friendship is not transitive, and it is not inherited

In some cases it is convenient define a function as a friend instead as a member.

Because a friend is activated in another way than a member: f(x,y) versus x.f(y).

Friends - Example 1
Slide Annotated slide Contents Index
References 

A function needs access to private variables in two classes

Program: A function f get access to the private state of both class A and B.
// A function f is a friend of both class A and B.
// f is outside both A and B.

#include <iostream>
#include <string>

class B; 

class A{
private:
  double a;

public:
  friend double f(A &aObj, B &bObj); 

  A(double a):a(a){}

  void ma();

  void print(){
    std::cout << a << std::endl;
  }
};

class B{
private:
  double b;

public:
  friend double f(A &aObj, B &bObj); 

  B(double b):b(b){}

  void mb();

  void print(){
    std::cout << b << std::endl;
  }
};

void A::ma(){
    a += 3.0;
}

void B::mb(){
    b += 4.0;
}

double f(A &aObj, B &bObj){
  return aObj.a + bObj.b;
} 

int main(){
  A aObj(1);
  B bObj(2);

  std::cout << f(aObj,bObj) << std::endl;  // 3
 
  aObj.print();  // 1
  bObj.print();  // 2
}

Matrix Vector multiplication is a compelling, practical example of this approach, see §11.5 in The C++ Programming Language

Friends - Example 2
Slide Annotated slide Contents Index
References 

Given two classes A and B with private state a and b respectively

We illustrate various possibilities and challenges of providing friendship from one class to the other

Program: Class A provides friendship to class B.
// Class B is a friend of class A, such that methods in
// class B can access private variables in class A.
// It is important that class A is defined before class B,
// because the details of class A are needed in mb.

#include <iostream>
#include <string>

class B;   // forward declaration, B is an incomplete class

class A{
private:
  double a;

public:
  friend class B;         // B must be known here!
  A(double a):a(a){}

  void ma(){
    a += 3.0;
  }

  void print(){
    std::cout << a << std::endl;
  }
};

class B{
private:
  double b;

public:
  B(double b):b(b){}

  void mb(A &x){
    b += x.a;             // x.a is OK because class B is a friend of A 
  }

  void print(){
    std::cout << b << std::endl;
  }
};



int main(){
  A aObj(1);
  B bObj(2);

  aObj.ma();
  bObj.mb(aObj);

  aObj.print();  // 4
  bObj.print();  // 6
}

Program: Class A and B are mutual friends of each other - problems in this version.
// We want class A and B to be mutual friends of each other.
// A member function in each class uses a private variable in the other.
// Problematic because ma and mb are inlined in the classes:
// Inside class A we use details about class B, which the compiler
// is not aware of yet.

#include <iostream>
#include <string>

class B;

class A{
private:
  double a;

public:
  friend class B;
  A(double a):a(a){}

  void ma(B &x){
    a += x.b;                      // error: invalid use of incomplete type  struct B
}

  void print(){
    std::cout << a << std::endl;
  }
};

class B{
private:
  double b;

public:
  friend class A;
  B(double b):b(b){}

  void mb(A &x){
    b += x.a;
}

  void print(){
    std::cout << b << std::endl;
  }
};

int main(){
  A aObj(1);
  B bObj(2);

  aObj.ma(bObj);
  bObj.mb(aObj);

  aObj.print();  // 3
  bObj.print();  // 5
}

Program: Class A and B are mutual friends of each other - this version is OK.
// Class A and B are mutual friends of each other. A member function
// in each class uses a private variable in the other.
// It is important that the member functions ma and mb are defined
// after both classes. 

#include <iostream>
#include <string>

class B;

class A{
private:
  double a;

public:
  friend class B;
  A(double a):a(a){}

  void ma(B &x);

  void print(){
    std::cout << a << std::endl;
  }
};

class B{
private:
  double b;

public:
  friend class A;
  B(double b):b(b){}

  void mb(A &x);

  void print(){
    std::cout << b << std::endl;
  }
};

void A::ma(B &x){    // Member functions must be defined outside the 
    a += x.b;        // classes
} 

void B::mb(A &x){
    b += x.a;
} 

int main(){
  A aObj(1);
  B bObj(2);

  aObj.ma(bObj);
  bObj.mb(aObj);

  aObj.print();  // 3
  bObj.print();  // 5
}

Program: A variant were we want ma to be a friend of B and mb to be friend of A - problems in this version.
// A version were we would like that a *specific member function* of A gets access
// to the private members in B. And symmetrically, the other way around.
// The symmetric case, where a member function of B gets access to private members in A,
// is not easy to deal with.
// Therefore class B as such is a friend of A in this version.

#include <iostream>
#include <string>

class B; 

class A{
private:
  double a;

public:
  friend class B;
//friend void B::mb(A &x);   // invalid use of incomplete type  struct B.
                             // mb in B is unknown at this location in the source file.
  A(double a):a(a){}

  void ma(B &x);

  void print(){
    std::cout << a << std::endl;
  }
};

class B{
private:
  double b;

public:
  friend void A::ma(B &x);  // ma of class A get access to 
                            // private parts of B.
  B(double b):b(b){}

  void mb(A &x);

  void print(){
    std::cout << b << std::endl;
  }
};

void A::ma(B &x){
    a += x.b;
}

void B::mb(A &x){
    b += x.a;
}

int main(){
  A aObj(1);
  B bObj(2);

  aObj.ma(bObj);
  bObj.mb(aObj);

  aObj.print();  // 3
  bObj.print();  // 5
}

Friends - class Point - notational convenience
Slide Annotated slide Contents Index
References 

We wish to use the notation Move(p, dx,dy) instead of p.Move(dx,dy)

Program: Class Point - header file.
// A variant of Point with two Move friends: Move1 and Move2.
// These are defined near main in prog.cc

class Point {
private: 
  double x, y;

public:
  friend void Move1(Point&, double, double);         // We show two slightly different
  friend Point Move2(const Point&, double, double);  // version of the Move function.

  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Class Point - implementation - not important for this example.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p){
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: The program including implementation of friend moving functions.
#include <iostream>
#include "point.h"

using namespace std;

// Friend of Point. Mutate p.
void Move1(Point &p, double dx, double dy){
  p.x += dx; p.y += dy;
}

// Friend of Point. Do not mutate p. Return moved copy of Point.
Point Move2(const Point &p, double dx, double dy){
  return Point(p.x + dx, p.y + dy);             // point copied out
}

int main(){

  Point p(1,2), q(3,4), r, s;

  p.move(1,1);        // Mutate p, with the member function move.
  Move1(q,1,1);       // Mutate q by the friend Move 1 - maybe misleading!
  r = Move2(p,1,1);   // Make a moved point by the friend Move2.

                      // Which of the moving abstractions do we prefer?

  cout << "Point p: " << p << endl;   // (2,3)
  cout << "Point q: " << q << endl;   // (4,5)
  cout << "Point r: " << r << endl;   // (3,4)
}

Which moving operation do we prefer? The move member, Move1, or Move2 ?

Friends - Class Point - operator friends
Slide Annotated slide Contents Index
References 

We wish that the overloaded operator<< for class Point is a friend

This particular operator cannot be defined as a member of class Point, because the point is the second operand

It is, in general, attractive that this and other operators get access to the private state of the class

Program: Class Point - header file.
class Point {
private: 
  double x, y;

public:
  friend std::ostream& operator<<(std::ostream&, const Point&);
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point);
};

Program: Class Point - implementation - including the implementation of operator<<.
#include <cmath>
#include <iostream>
#include "point.h"

// The operator is a friend of class Point:
std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.x << "," << p.y << ")" ;
}


Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p){
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

Program: The program that uses the operator - nothing new here.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p(1.0, 2.0),
        q(3.0, 4.0);

  p.move(1,1);
  q.move(1,1);

  cout << "Point p: " << p << endl;   // (2,3)
  cout << "Point q: " << q << endl;   // (4,5)
}

Friends - Class Point - implicit conversion
Slide Annotated slide Contents Index
References 

The left operand of the dot operator cannot be implicit converted.

The parameters of a friend function can be used instead.

Reference
  • The C++ Programming Language: Page 281

Program: Class Point - header file.
class Point {
private: 
  double x, y;

public:
  friend Point Move(const Point&, double, double);  

  Point(double, double);
  Point(double);                  // We rely on this constructor in the example:
                                  // Implicit conversion from double to point.
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Class Point - implementation - including the implementation of operator<<.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(double coord): x(coord), y(coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p){
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: The program that use the operator.
#include <iostream>
#include "point.h"

using namespace std;

// Friend of Point. Returns moved copy of p.
Point Move(const Point &p, double dx, double dy){
  return Point(p.x + dx, p.y + dy);
}

int main(){

  Point p(1,2), q(3,4), r;

  Point(3).move(1,2);        // Moving temporary Point - not useful.
//3.move(1,2);               // Point(3).move(1.2) is not attempted.  
                             // No implicit conversion on leftmost operand of .
  r = Move(3,  1,2);         // Implicit conversion of 3.0 to the point (3,3).
                             // Moving this point to (4,5).

  cout << "Point p: " << p << endl;   // (1,2)
  cout << "Point q: " << q << endl;   // (3,4)
  cout << "Point r: " << r << endl;   // (4,5)
}

Discussion - Encapsulation, Visibility and Access
Slide Annotated slide Contents Index
References 

Is access control really important in OOP?

Is it really necesary to introduce yet another mechanism, such as friends, to deal with access control?

In C# it is possible to access a private instance variable thorugh a public property of (more or less) the same name. Is this related to our discussion of access control (and to friends)?

  • Minimize the number of methods that relies on the private state of an object

    • Reconsider if protected data members are really needed

    • Reconsider the use of friends

    • Prefer non-member non-friend functions to member functions [Effective C++, Third edition item 23]

Exercise 3.6. Discuss encapsulation, visibility and accessDiscusss encapsulation, visibility and access, based on the enclosing slide.

Operator overloading
Slide Annotated slide Contents Index
References 

C++ supports comprehensive overloading of existing operators

Reference
  • The C++ Programming Language: Page 261 - ...

  • Restrictions on operator overloading

    • It is not possible to invent new operator symbols.

    • The associativity and precedence of the predefined rules of predefined operators apply

    • The following operators cannot be overloaded: :: (scope), . (member selection), .* (member selection through pointer), ?: (conditional, ternary), sizeof, typeid.

    • At least one operand must be of a user-defined type

    • If the left operand is of user-defined type, the operator may be defined as a member function

    • If the left operand is a predefined type, the operator must be a non-member function

For some operators, special rules and interpretations apply

Reference
  • The C++ Programming Language: Page 286 - ...

Example: Operator overloading in class Point
Slide Annotated slide Contents Index
References 

We program a few funny Point operators: ++, +, and ==

Both as members and as friends

Program: Class Point with operators as members.
// Operator overloading - member functions.

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;

  Point operator+(const Point&);
  Point operator++(int);   // int means Postfix ++
  bool  operator==(const Point&);
  
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Definition of Point member functions.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

Point Point::operator+(const Point& p){
  return Point(x+p.x, y+p.y);
}

Point Point::operator++(int){   // int means Postfix ++
  x++; y++; 
  return *this;
}

bool Point::operator==(const Point& p){
  return std::abs(p.x - x) <= 3.0 && std::abs(p.y - y) <= 3.0;
}

std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: A program that illustrates uses the Point operators.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p(1,2),
        q(3,4),
        r(10,12);

  cout << "Point p: " << p << endl;    // (1,2)
  cout << "Point q: " << q << endl;    // (3,4)
  cout << "Point r: " << r << endl;    // (10,12)

  cout << "Point q and q are" << ((p==q) ? "" : " not") << " equal" << endl;   // equal
  cout << "Point p and r are" << ((p==r) ? "" : " not") << " equal" << endl;   // NOT equal

  p++; q++;
  cout << "After p++: Point p: " << p << endl;  // (2,3)
  cout << "After q++: Point q: " << q << endl;  // (4,5)

  const Point &t = p + q;
  cout << "Point t = p + q: " << t << endl;     // (6,8)
}

Program: Program output.
Point p: (1,2)
Point q: (3,4)
Point r: (10,12)
Point q and q are equal
Point p and r are not equal
After p++: Point p: (2,3)
After q++: Point q: (4,5)
Point t = p + q: (6,8)

Program: Class Point with non-member operators.
// Same example - but with non-member operator overloads. Friends are used instead.

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;

  friend Point operator+(const Point&, const Point&);
  friend Point operator++(Point&, int);  
  friend bool  operator==(const Point&, const Point&);
};

std::ostream& operator<<(std::ostream&, const Point&);

Program: Definition of Point member functions and non-member operators.
#include <cmath>
#include <iostream>
#include "point.h"

Point::Point(double x_coord, double y_coord): x(x_coord), y(y_coord){
}

Point::Point(): x(0.0), y(0.0){
}

double Point::getx () const{
  return x;
}

double Point::gety () const{
  return y;
}

void Point::move(double dx, double dy){
    x += dx; y += dy;
}

double Point::distance_to(Point p) const{
    return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}

Point operator+(const Point& p, const Point& q){
  return Point(p.x + q.x, p.y + q.y);
}

Point operator++(Point& p, int){  
  p.x++; p.y++; 
  return p;
}

bool operator==(const Point& p, const Point& q){
  return std::abs(p.x - q.x) <= 3.0 && std::abs(p.y - q.y) <= 3.0;
}


std::ostream& operator<<(std::ostream& s, const Point& p){
  return s << "(" << p.getx() << "," << p.gety() << ")" ;
}

Program: An identical program that illustrates uses the Point operators.
#include <iostream>
#include "point.h"

using namespace std;

int main(){

  Point p(1,2),
        q(3,4),
        r(10,12);

  cout << "Point p: " << p << endl;    // (1,2)
  cout << "Point q: " << q << endl;    // (3,4)
  cout << "Point r: " << r << endl;    // (10,12)

  cout << "Point q and q are" << ((p==q) ? "" : " not") << " equal" << endl;   // equal
  cout << "Point p and r are" << ((p==r) ? "" : " not") << " equal" << endl;   // NOT equal

  p++; q++;
  cout << "After p++: Point p: " << p << endl;  // (2,3)
  cout << "After q++: Point q: " << q << endl;  // (4,5)

  const Point &t = p + q;
  cout << "Point t = p + q: " << t << endl;     // (6,8)
}

Program: Program output.
Point p: (1,2)
Point q: (3,4)
Point r: (10,12)
Point q and q are equal
Point p and r are not equal
After p++: Point p: (2,3)
After q++: Point q: (4,5)
Point t = p + q: (6,8)


Collected references
Contents Index
The C++ Programming Language: Page 234,849
Namespaces - logical program organization
The C++ Programming Language: Page 240
Effective C++, Third edition: Item 23
The C++ Programming Language: Page 226, 247, 270
Effective C++, Third edition: Item 4
The C++ Programming Language: Page 242, 244.
The C++ Programming Language: Page 364-367
The C++ Programming Language: Page 367
auto_ptr at cplusplus.com
The C++ Programming Language: Page 229
The C++ Programming Language: Page 245-246
Effective C++, Third edition: Item 10
Effective C++, Third edition: Item 6
Type conversion in general
The C++ Programming Language: Page 228
Effective C++, Third edition: Item 4, and 18 (page 80)
Effective C++, Third edition: Item 3
The C++ Programming Language: Page 232
The C++ Programming Language: Page 230
The C++ Programming Language: Page 235, 144, 199
The C++ Programming Language: Page 236-242
The C++ Programming Language: Page 278-282
The C++ Programming Language: Page 281
The C++ Programming Language: Page 261 - ...
The C++ Programming Language: Page 286 - ...

 

Chapter 3: Abstraction Mechanisms, Part 1
Course home     Author home     About producing this web     Previous lecture (top)     Next lecture (top)     Previous lecture (bund)     Next lecture (bund)     
Generated: March 26, 2013, 13:03:29