Chapter 4
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. We first concentrate on constructors and destructors. Next we look at the RAII (Resource acquisition is initialization) idea in C++, including a discussion of some of the C++11 smart pointers. We also study how to program the meaning of object copying - in copy constructors and copy assignment. Visibility and access, in particular friends, is also covered. Finally we go into operator overloading for user defined types.

Classes, structs and namespaces
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Jeg viser en struct og et namespace i programmet. På sliden involverer jeg også namespaces, for at sætte klasser/structs på plads ift. namespaces. Brug gerne link tilbage til slide om namespaces. Måske skal jeg ikke bruge tid på de tre programmer - de var ude i F14.

Structs and classes are similar in C++

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

Reference
  • The C++ Prog. Lang. (3. edition): Page 234,849

Reference
  • The C++ Prog. Lang. (4. edition): Page 454

  • 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

    • There is a global namespace

Reference

Class basics
Slide Contents Index
References 

We introduce classes in C++ in a series of versions of class Point - starting with a C# counterpart

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.
// A simple C++ class - in transition from the 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: The class design of class Point.
// Class Point definition - in a header filer. Declaration of operator<< outside the class.  
// Class design (class definition) separated from class implementation (member definition).

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;
};

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

Program: Member function definitions for class Point.
// Definition of member functions for class Point

#include <cmath>      // sqrt
#include <iostream>
#include "point4.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));
}

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

Program: Class Point - with in-class initializations.
// Illustration of in-class initializers (C++11) - a minor extension in C++11

class Point {
private: 
  // Data members have in-class initializers - not allowed in C++ before C++11
  double x {1.1},              // overridden by constructors
         y {2.1},              // overridden by constructors
         scaling_factor {2};   // common initialization accross constructors

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

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

Program: Definition of function members.
// Using in-class initialized data member is several constructors.

#include <cmath>
#include <iostream>
#include "point5.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 * scaling_factor;
}

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

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));
}

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

In C++ Class design is emphasized by separating class definition from member definition

Functions and variables outside classes
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++ Prog. Lang. (3. edition): 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(...) if f is a static member

      • Other functions, such as the entry function main

  • Variables outside classes:

    • Global variables and variables in namespaces

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

Compared with Java and C#: In C++ there is less emphasis on static variables and methods

Constructors
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Læg vægt på den specielle initialiseringssyntax. Det ville være mere primitivt og unødvendigt komplekst hvis assignment var brugt i kroppen. Munder ud i en opgave om methoder i Point og Rectangle. Tilgang til hjørnepunkterne i et rectangel, og muligheden for evt. at flytte rektanglet ved at flytte dets hjørnepunkter. Kræver god brug af references - lidt tricket faktisk.

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

Reference
  • The C++ Prog. Lang. (3. edition): Page 226, 247, 270

Reference
  • The C++ Prog. Lang. (4. edition): Page 455, 501, 502

Program: A class with member initializers.
C::C(formal_parameters) : member_initializers {
  commands;
}

  • Initialization of class data members with member initializers

    • Special syntax for initialization of data members and bases (super class parts)

    • Emphasizing uniform use of {} notation

    • Placed between the constructor header and the body

    • Initializes the data members from the formal parameters of the constructor

    • Constructor chaining, also known as delegating constructors - C++11

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

References

Constructors - examples
Slide Contents Index
References 

We show examples of various aspects of constructors

Program: The Point class.
// We are about to illustrates constructors with constructor chaining in C++ 11

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.
// Definition of constructors with constructor chaining - C++11

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


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

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

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

Program: The Rectangle class.
// Constructors in class Rectangle - more or less the same as in class Point.

#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.
// Definition of methods with constructor chaining for class Rectangle 

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

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

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

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

Program: Rectangle constructors - with default initialization and subsequent assignments - BAD STYLE.
// Alternative definitions of Rectangle constructors. BAD STYLE. 

#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(double x1, double y1, double x2, double y2){
  upper_left = Point(x1,y1);
  lower_right = Point(x2,y2);
}

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

Program: Sample Point and Rectangle constructions.
// Sample construction of points and rectangles

#include <iostream>
#include "rect.h"
// In this variant of the program, point.h is included via rect.h 

int main(){
  using namespace std;

  Point p1{1, 2}, p2{5, -3};                  // Two points on the stack. 

                                              // Four rectangles on the stack:
  Rectangle r1{},                             // Use default constructor
            r2{1, 2, 5, -3},                  // Rectangle from x and y coordinates
            r3{p1, p2},                       // Rectangle from two corner points
            r4{Point{1, 2}, Point{5, -3}};    // Same

  // use of points and rectangles
  // ...
}

More about constructors
Slide Contents Index
References 

Additional properties of constructors in C++

Reference
  • The C++ Prog. Lang. (3. edition): Page 226, 247, 270

Reference
  • The C++ Prog. Lang. (4. edition): Page 455

  • Explicit constructors

    • By means of an explicit specifier before the constructor

    • Cannot be used in implicite type conversion

  • Deleting with = delete in C++11

    • It is possible to delete a constructor (or any other function) that has been generated automatically

    • For instance deleting a copy constructor, such that an object cannot be copied

  • Defaulting with = default in C++11

    • It is possible to emphasize that we actually want to have an automatically generated function

    • For instance to document that the automatically generated copy constructor exists

Reference
  • The C++ Prog. Lang. (4. edition): explicit, default, delete. Page 457, 519, 524

Examples on the next slide

Examples of explicit, deleted, and defaulted constructors
Slide Contents Index
References 

Examples that illustrate explicit, deleted and defaulted constructors

Program: The Point class - with explicit and deleted constructors.
// Class Point with explicit constructor, and deleted copy constructor.

class Point {
private: 
  double x, y;

public:
  Point();                    
  explicit Point(double x);             // Cannot be used in implicit type conversions
  Point(double x, double y);  
  Point(const Point&) = delete;         // The default generated copy constructor cannot be used

  // other members
};

Program: Implementation of the Point class constructors.
// Definition of constructors - nothing of particular interest.

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


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

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

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

Program: Attempting Point constructions.
// Attempting construction of points.

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

using namespace std;

int main(){
  Point p1{1, 2},
        p2{3},
        p3{p1},                     // Error: No copy constructor.
        p4, p5, p6;

  p4 = 4.0;                         // Error: explicit Point(double) cannot be used in implicit type conversion.
  p5 = static_cast<Point>(4.0);     // OK
  p6 = Point(4.0);                  // OK

  // use of points here
  // ...
}

Program: The Point class - with explicit and defaulted constructors.
// Class Point with explicit constructor, and defaulted copy constructor.

class Point {
private: 
  double x, y;

public:
  Point();                    
  explicit Point(double x);             // Cannot be used in implicit type conversions
  Point(double x, double y);  
  Point(const Point&) = default;        // We want to have the default generated copy constructor

  // other members
};

Program: Implementation of the Point class constructors.
// Definition of constructors - nothing of particular interest.

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


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

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

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

Program: Point constructions - now OK.
// Attempting construction of points. Now copy construction is OK.

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

using namespace std;

int main(){
  Point p1{1, 2},
        p2{3},
        p3{p1};                     // OK: We can use the copy constructor.

  // use of points here
  // ...
}

Use of constructors
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Spørg de studerende om output af klientprogrammet - reveal på slide. Hold musen nede og kør frem...

We illustrate the use of overloaded constructors - using funny Point initializations

  • Four funny Point constructors:

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

    • Point(double x, double y): (x, y)

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

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

Program: Class Point with a variety of constructors.
// Class Point with funny constructors.

class Point {
private: 
  double x, y;

public:
  Point();                    // default constructor:  (0,7)
  Point(double x);            // (x, 20)
  Point(double x, double y);  // (x, y)  
  Point(const 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 the 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(const 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 local variables of class type.
//Point(): (0,7)        Point(double x): (x, 20)      Point(double x, double y): (x, 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. p is (0,7) at this location.
  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. t is (3,20).
  cout << "Point u: " << u << endl;   // (4,22)

  Point v{t};                         // Same. t is (3,20).
  cout << "Point v: " << v << endl;   // (4,22)


  Point ap[10];                       // The default constructor used 10 times
  for(int i = 0; i < 10; i++)         
     cout << "ap[" << i <<            // (0,7) ten times
     "]: " << 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)
Point v: (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 q: " << q << endl;   // (11,12)  - because of the assignment q = r
  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();
}

Program: An additional Point construction attempt - with use of uniform default initialization.
#include <iostream>
#include "point.h"

using namespace std;

int f(){

  Point p, q,                         // The default constructor is used for both p and q  
        q1{},                         // Default initialization. The same as for p and q, but prefer {}.

        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.
        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 q: " << q << endl;   // (11,12)
  cout << "Point q1: " << q1 << endl; // (0,7)  
  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 4.2. 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.

Move Constructors - C++11
Slide Contents Index
References 

A move constructor in class C defines move semantics of C-objects

A move constructor takes an Rvalue references as parameter

Reference

Reference
  • The C++ Prog. Lang. (4. edition): Page 514

  • Moving

    • Giving away details of an object instead of copying the details - "destructive read"

    • A move constructor C(C&& a) moves a into the current object

      • The object a must be left in a state that the destructor can deal with (cheaply).

    • A move constructor does not allocate resources - and should not throw any exception

      • In constrast to a copy constructor

    • The compiler can - in some situations - decide to use a move constructor instead of a copy constructor

      • Alternatively, the programmer can enforce a move by casting to an Rvalue reference - using the move function.

    • If moving should take place, and if no move function constructor exist, use the copy constructor (and pay the price).

Use of move constructors
Slide Contents Index
References 

We illustrate the use of move constructors with funny constructors

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

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

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

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

Program: Funny implementations of the 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(const Point& p): x(p.x + 1.0), y(p.y + 2.0){
}

Point::Point(Point&& p): x(p.x - 3.0), y(p.y - 4.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 local variables of class type.
//Point(): (0,7)                    Point(double x): (x, 20)           Point(double x, double y): (x, y)     
//Point(Point& p) (p.x+1, p.y+2)    Point(Point&& p) (p.x-3, p.y-4) 
//Reveal...

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

using namespace std;

//A Point identity function - revealing the parameter p by printing on standard output.
Point pf(Point p){
  cout << "Inside pf: Parameter: " << p << endl;
  return p;
}

int f(){
  Point p;
  cout << "Point p: " << p << endl;   // (0,7)


  Point q = pf(p);                    // Passing p to pf via copy constructor: (1,9)
  cout << "Point q: " << q << endl;   // (-2,5) -  because pf handles return via the move constructor. 


  Point r = pf(Point{10,12});         // Constructing point (10,12) via Point(double,double). Passing it bitwise!?
  cout << "Point r: " << r << endl;   // (7,8).  Returning via use of move constructor. 


  Point s = pf(move(Point{10,12}));   // Passing Rvalue point (10, 12) to pf via forced used of move constructor (7,8). 
  cout << "Point s: " << s << endl;   // (4,4) -  because pf handles return via the move constructor. 
}

int main(){
  f();
}

Program: Same program with more comments.
//Commented version of the previous program. 
//Point(): (0,7)                    Point(double x): (x, 20)           Point(double x, double y): (x, y)     
//Point(Point& p) (p.x+1, p.y+2)    Point(Point&& p) (p.x-3, p.y-4) 

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

using namespace std;

//A Point identity function - revealing the parameter p by printing on standard output.
Point pf(Point p){
  cout << "Inside pf: Parameter: " << p << endl;
  return p;
}

int f(){
  Point p;
  cout << "Point p: " << p << endl;   // (0,7)

  // COPYING INTO, MOVING OUT FROM:
  Point q = pf(p);                    // Passing p to pf via copy constructor: (1,9)
  cout << "Point q: " << q << endl;   // (-2,5) -  because pf handles return via the move constructor. 

  // BITWISE PASSING INTO, MOVING OUT:
  Point r = pf(Point{10,12});         // Constructing point (10,12) via Point(double,double). Passing it bitwise!?
  cout << "Point r: " << r << endl;   // (7,8).  Returning via use of move constructor. 

  // MOVING INTO, MOVING OUT:
  Point s = pf(move(Point{10,12}));   // Passing Rvalue point (10, 12) to pf via forced used of move constructor (7,8). 
  cout << "Point s: " << s << endl;   // (4,4) -  because pf handles return via the move constructor. 
}

int main(){
  f();
}

Program: Program output.
Point p: (0,7)
Inside pf: Parameter: (1,9)
Point q: (-2,5)
Inside pf: Parameter: (10,12)
Point r: (7,8)
Inside pf: Parameter: (7,8)
Point s: (4,4)

Exercise 4.3. Move constructors and copy constructors

Take a look at this variant of class Point from above - all parts in one file. Notice the difference between the parameters of the function pf above, and pf in the following version of the program.

#include <cmath>
#include <iostream>

using namespace std;

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(const Point& p);      // copy constructor: (p.x+1, p.y+2) 
  Point(Point&& p);           // move constructor: (p.x-3, p.y-4) 

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

std::ostream& operator<<(std::ostream&, const 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(const Point& p): x(p.x + 1.0), y(p.y + 2.0){
}

Point::Point(Point&& p): x(p.x - 3.0), y(p.y - 4.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() << ")" ;
}


const Point& pf(const Point& p){
  cout << "Inside pf: Parameter: " << p << endl;
  return p;
}

int f(){
  Point p;
  cout << "Point p: " << p << endl;   

  Point q = pf(p);                    
                                      
  cout << "Point q: " << q << endl;   

  Point r = pf(Point{10,12});         
                                      
  cout << "Point r: " << r << endl;   

}

int main(){
  f();
}

Predict the results of the program. More specifically, which constructors are used in f?

Do you find it easy and natural to decide which kind of constructors are used for parameter passing and value return?

Destructors
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Programmerne på denne side har en sladrehank destructor. Det går kun ud på at forstå hvornår en destructor bliver kaldt. Endvidere gør vi pointen at en pointer til et objekt ikke destrueres. VIGTIG. Går igen på næste slide...

Destructors are essential for resource management purposes in C++

Most classes do not need destructors

Destructors are called implicitly

Reference
  • The C++ Prog. Lang. (3. edition): Page 242, 244.

Reference
  • The C++ Prog. Lang. (4. edition): Page 485-489

  • Destructor activation:

    • When an automatic (local) 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(const 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(const 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 would destruct 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 Contents Index
References 
Intern kommentar til forelæsningen om denne slide:

Vi arbejder nu med Points med dynamisk allokeret repræsentation (en poiner til noget). Første programgruppe illustrerer leak. Destructor klades for statisk allokerede variable, men den udfører ikke sit arbejde. VIGTIG POINTE: DESTRUCTOR KALDES IKKE PÅ POINTER (TIL EN POINT) - s i eksemplet.

Næste programgruppe illustrerer en bedre destructor - som deallokerer ressourcen. PLUS KALD AF DELETE PÅ s.

Opgaven introducerer en forkert copy constructor, som introducerer shared point representation. Med dette flyttes kopien når originalen flyttes (og omvendt) - pga. shared representation. Endvidere vil destructoren ikke virke, fordi den dynamisk alokerede repræsentation deletes to gange.

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 - representated as a dynamically allocated array of two doubles .
class Point {
private: 
  double *point_representation;   // A pointer to an array of two doubles

public:
  Point();                        // (0,0) 
  Point(double x, double y);      // (x,y)
  ~Point();                       // Destructor. Empty as of now.

  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 dynamically allocated memory for p, q, r, ap[0] and ap[1] is 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.
y:/Kurt/Files/Advanced-programming-cpp/cpp/kn/destructors-2a/point.h

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

// Here we use the member initializer to establish the array of two doubles
Point::Point() : point_representation{new double[2]{0.0, 0.0}} {
}

Point::Point(double x_coord, double y_coord) : point_representation{new double[2]{x_coord, 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 explicit 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 4.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 version 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(const 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]{0.0, 0.0}} {
}

Point::Point(double x_coord, double y_coord) : point_representation{new double[2]{x_coord, y_coord}}{
}

Point::Point(const 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 (using the move member function) 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 Contents Index
References 

There is 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++ Prog. Lang. (3. edition): Page 364-367

Reference
  • The C++ Prog. Lang. (4. edition): Page 64, 112, 356

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;          // Encapsulaton of resource in a class
  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 on the stack

  // ...

  // 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.
// Complete program that illustrates the RAII idea. Does compile.
// This version just reveals the resource allocation and deallocation on standard output. 
// Just a detailed setup that illustrates that the resource always is released - 
// in both normal return situations, and in exceptional situations.

#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 an 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 - normal run.
g++ raii-pattern-1.cc
a.exe

The program will use resource without throwing an exception
Allocate resource: sample_resource
Use Resource
Release resource: sample_resource

Program: Program output - with problem_condition.
g++ raii-pattern-1.cc
a.exe problem

The program will throw an exception during use of resource
Allocate resource: sample_resource
Use Resource
An exception is thrown
Release resource: sample_resource
Recovering

Smart pointers in C++11: unique_ptr and shared_ptr
Slide Contents Index
References 

A smart pointer encapsulates a pointer in a object - it acts as a resource handle

Smart pointers use the RAII idea

Reference
  • The C++ Prog. Lang. (4. edition): Page 112-114, 987-993

  • unique_ptr

    • A unique pointer owns the encapsulated pointer

    • A unique pointer cannot be copied - it can be moved

    • The object pointed to is deleted when the unique pointer is destroyed (as an object that goes out of scope, for instance)

  • shared_ptr

    • Two or more shared pointers can encapsulate pointers to the same underlying object

    • Shared pointers keep track of the number of pointers to a given object - reference counting

      • Via use of a separate manager object

    • The object pointed to is deleted when no more shared pointer objects refer to it

Reference
  • Effective Modern C++: unique_ptr. Item 18

Reference
  • Effective Modern C++: shared_ptr. Item 19

Example use of unique_ptr and shared_ptr
Slide Contents Index
References 

We study the use of raw pointes, unique_ptr, and shared_ptr

These examples are reserved for the exercise session

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();
  ~Point();
  double getx () const;
  double gety () const;
  void displace(double, double);   // Called move in other contexts
  double distance_to(const Point&);
};

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

Program: The usual implementation of class Point - nothing of particular interest.
// Member definitions for the Point class. Nothing of particular interest.

#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(){
  std::cout << "Point deleted" << std::endl;
}

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

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

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

double Point::distance_to(const 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: An illustration of raw pointers.
// Programming with C-style, raw pointers to Point, before we illustrate the use of smart pointers.

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

using namespace std;

void f(Point *pp1){
  Point *ap1{pp1},                     // Copy pointer to ap1
        *ap2{new Point(3,4)};          // Allocate second point here. ap2 points to it.

  // Do something...
  cout << "ap1: " << *ap1 << endl;     // (1,2)
  cout << "ap2: " << *ap2 << endl;     // (3,4)

  ap2->displace(1,1);
  cout << "ap2: " << *ap2 << endl;     // (4,5)

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

  ap2 = ap1;                           // Copy a pointer. (3,4) is lost here.

  // Print ap1 and ap2
  if (ap1)                             // (1,2)
     cout << "ap1: " << *ap1 << endl;  
  else
     cout << "ap1 is nullptr" << endl;

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

  // Both points survive on the heap. Memory for (3,4) leaks!
}

int main(){
  Point *p = new Point(1,2);           // Allocate first point here
  f(p);                                // Pass pointer to (1,2) to f.

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

Program: Program output.
ap1: (1,2)
ap2: (3,4)
ap2: (4,5)
ap1-ap2 dist: 4.24264
ap1: (1,2)
ap2: (1,2)
p: (1,2)

Program: An illustration of unique_ptr<Point> - same example as for raw pointers.
// Same example programmed with unique_ptr

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

using namespace std;

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

  // Use unique_ptr<Point> in the same way as Point* is used:
  cout << "ap1: " << *ap1 << endl;   // (1,2)
  cout << "ap2: " << *ap2 << endl;   // (3,4)

  ap2->displace(1,1);
  cout << "ap2: " << *ap2 << endl;   // (4,5)

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

  // Destructive copying:  The point referred by ap2 is deleted.
  //                       The pointer encapsualated in ap1 is moved to ap2.
  //                       ap1 is unbound (becomes nullptr).
  cout << "Now assigning ap1 to ap2" << endl;
  ap2 = move(ap1);         // Short form or  ap2 = static_cast<unique_ptr<Point>&&>(ap1);

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

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

  // ap1 and ap2 are deleted on exit from f - out of scope.
}

int main(){
  Point *p = new Point(1,2);   // Better to make a smart pointer here, and pass it to p. 
  f(p);                        // See the exercise!

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

Program: Program output.
ap1: (1,2)
ap2: (3,4)
ap2: (4,5)
ap1-ap2 dist: 4.24264
Now assigning ap1 to ap2
Point deleted
ap1 is nullptr
ap2: (1,2)
Point deleted
p: (1.0287e+160,2)

Exercise 4.6. A variant of the unique_ptr program

The program above passes a raw pointer to a Point to the function f. It is a much better idea to wrap p (the point (1,2)) in a unique_ptr already in main, and to pass a unique_ptr to f. Provide for this change.

In addition we want to return (the ownership of) the unique_ptr in a2 back to main via a value return. Please do that.

In this exercise, please be aware that unique pointers cannot be copied - they must be moved.

Program: An illustration of shared_ptr<Point> - same example as for unique_ptr<Point>.
// Same example as for unique_ptr, just using shared_ptr instead

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

using namespace std;

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

  cout << "ap1: " << *ap1 << endl;   // (1,2)
  cout << "ap2: " << *ap2 << endl;   // (3,4)

  ap2->displace(1,1);
  cout << "ap2: " << *ap2 << endl;   // (4,5)

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

  cout << "Now assigning ap1 to ap2" << endl;

  // Normal copying assignment:   The point referred by ap2 (3,4) is deleted here.
  //                              The pointer encapsulated in ap1 is copied to ap2.
  ap2 = ap1;                      // Now both ap1 and ap2 points at (1,2)
                                     
  if (ap1.get())                        // (1,2)
     cout << "ap1: " << *ap1 << endl;  
  else
     cout << "ap1 is nullptr" << endl;

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

  // ap1 and ap2 go out of scope
  // The point (1,2) is deleted here.
}

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

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

Program: Program output.
ap1: (1,2)
ap2: (3,4)
ap2: (4,5)
ap1-ap2 dist: 4.24264
Now assigning ap1 to ap2
Point deleted
ap1: (1,2)
ap2: (1,2)
Point deleted
p: (1.02874e+160,2)

Exercise 4.6. A variant of the shared_ptr program

Do a similar exercise for shared_ptr, as we did for unique_ptr above (see here). The starting point is this program .

More specifically, from main pass a shared pointer to f. Do NOT return the pointer from f. f should be void.

Does the shared pointer survive when we return from f? What is the output in the last line of main?

Program: Another simple illustration of shared_ptr<Point>.
// Another simple example of shared_ptr (not directly related to the previous examples). 

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

using namespace std;

int f(){
  shared_ptr<Point> pp1{new Point{1,2}},
                    pp2{new Point{3,4}},
                    pp3;

  cout << *pp1 << endl;    // (1,2)
  cout << *pp2 << endl;    // (3,4)

  pp1.reset();             // empty pp1 - delete underlying point (1,2) from the heap.

  pp3 = pp2;               // both pp3 and pp2 refer to (3,4).
  pp3->displace(1,1);      // displace point pointed to by pp3

  cout << *pp2 << endl;    // (4,5) - Point pointed to by pp2 has moved!

  cout << "Exit f" << endl;  
                           // Delete the other point (3,4) from the heap.
                           // Thus both points have disappeared from the heap at this time.
}

int main(){
  f();
  cout << "After f()" << endl;
  // All points on the heap have been released at this point.
}

Program: Program output.
(1,2)
(3,4)
Point deleted
(4,5)
Exit f
Point deleted
After f()

Program: Similar program with raw pointers.
// Similar program - with raw pointers

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

using namespace std;

int f(){
  Point *pp1{new Point{1,2}},
        *pp2{new Point{3,4}},
        *pp3;

  cout << *pp1 << endl;    // (1,2)
  cout << *pp2 << endl;    // (3,4)

  pp1 = nullptr;

  pp3 = pp2;               // both pp3 and pp2 refer to (3,4).
  pp3->displace(1,1);      // displace point pointed to by pp3

  cout << *pp2 << endl;    // (4,5) - Point pointed to by pp2 has moved!

  cout << "Exit f" << endl;  
}

int main(){
  f();
  cout << "After f()" << endl;
  // Both points (1,2) and (3,4) are still on the heap.
}

Program: Program output.
(1,2)
(3,4)
(4,5)
Exit f
After f()

Other examples of unique_ptr
Slide Contents Index
References 

We show a couple of additional examples, inspirred from The C++ Prog. Lang. (4. edition)

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();
  ~Point();
  double getx () const;
  double gety () const;
  void displace(double, double);     
  double distance_to(const Point&);
};

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

Program: The usual implementation of class Point - nothing of particular interest.
// Member definitions for the Point class. Nothing of particular interest. The destructor is revealing.

#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(){
  std::cout << "Point deleted: " << x << "," << y << std::endl;
}

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

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

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

double Point::distance_to(const 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: Unique_ptr versus raw pointer.
// RUN IT to see normal return of f, early return of f, and exception thrown in f. 
// q in f is destroyed in all cases (normal return, exception, early return)
// p in f is only destroyed upon normal return. *p is leaked upon early return and upon exeception.
// Inspired from Stroustrup 4ed, page 112.

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

class SomeProblem{};

void f(int i){
  Point *p {new Point{1,2}};                      // p is a raw pointer to a new point
  std::unique_ptr<Point> q{new Point{3,4}};       // q is a unique_ptr to a new point

  if (i == 10) throw SomeProblem{};               // In some cases, an exception is thrown
  if (i == 11) return;                            // In some cases, we have an early return

  p->displace(1,1);
  q->displace(1,1);

  delete p;
}

int main(){
  int i;
  std::cout <<  "Enter i: "; 
  std::cin >> i;

  try{
    f(i);
  }
  catch(SomeProblem){
    std::cout << "Some Problem" << std::endl;
  }
}

Program: Passing unique_ptr as parameter to a function.
// Illustrates how a unique pointer can be passed to a function.
// In f1 by value, and in f2 by const reference.
// Inspired from Stroustrup 4ed, page 989.

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

class SomeProblem{};

// Receives a (unique) pointer p by value, works on the pointed object, and returns it. 
std::unique_ptr<Point> f1(std::unique_ptr<Point> p){
  p->displace(1,1);
  return p;
}

// Receives a (unique) pointer p by const reference, and works on it. (No need for return).
void f2(const std::unique_ptr<Point>& p){
  p->displace(1,1);
}

int main(){

  std::unique_ptr<Point> p0{new Point{1,2}}, p1, p2;
  p1 = move(p0);                 // The pointer to Point{1,2} is moved from p0 to p1.   Must use move!
  p2 = f1(move(p1));             // ... and moved into f1, where the Point is operated on, 
                                 // and the pointer is moved out again via return. 
  std::cout << *p2 << std::endl; // (2,3)

  // The exanple same with f2 that uses a const reference:
  std::unique_ptr<Point> q0{new Point{1,2}}, q1;
  q1 = move(q0);                 // The pointer to Point{1,2} is moved from q0 to q1 
  f2(q1);                        // f2 works directly on q1 via a const reference. 
  std::cout << *q1 << std::endl; // (2,3)
                                 // point deleted (2,3)
                                 // point deleted (2,3)
}

About use of smart pointers in C++11
Slide Contents Index
References 

Some observations about use of shared_ptr and unique_ptr

  • Smart pointers can only be used with pointers to objects on the heap

    • Do not use smart pointers to objects on the stack

  • Pass pointers from new T expressions directly into constructers of smart pointers

    • Do not keep raw pointers in variables of their own

  • Avoid the creation of two or more manager objects for a single object on the heap

    • In other words: do not pass a given raw pointer to two or more smart pointers

Copying Point objects in parameter passing
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Klient programmet viser 3 funktioner g, h og i som overfører og tilbagefører punkter pr. value og reference i forskellige kombinationer. Værdioverførsel og værdireturn kalder copy constructor. Der benyttes funny constructors - så vi kan spore deres kald.

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++

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 with funny constructors.

class Point {
private: 
  double x, y;

public:
  Point();                    // default constructor:  (0,7)
  Point(double x);            // (x, 20)
  Point(double x, double y);  // (x, y)  
  Point(const 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(const 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 Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
I de viste programmer etableres der en situation hvor 3 linesegments deler et point array. Alle tre segmenter forsøger altså at deallokere det samme array (via pointere). Fejl anden gang.

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++ Prog. Lang. (3. edition): Page 245-246

Program: Class LineSegment WITHOUT copy constructor and and WITHOUT assignment operator. Header file.
// Point and LineSegments with default copy constructors - memberwise copying.

#include <iostream>

class Point {
private: 
  double x, y;

public:
  Point(double, double);
  Point();
  Point(const Point&) = default;               // Point has a default copy constructor
  double getx () const;
  double gety () const;
  void move(double, double);
  double distance_to(Point) const;
};

class LineSegment {
private: 
  Point *points;                               // A dynamically allocated array of points
  size_t number_of_points;
public:
  LineSegment(size_t s = 10);                  // Serves the role as default constructor.
  LineSegment(const LineSegment&) = default;   // LineSegment has a default copy constructor
  ~LineSegment();
  // ...
};

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

Program: The corresponding cc file - not particularly relevant.
// Member function definitions for Point and LineSegment - notice the LineSegment destructor.

#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) : number_of_points{s}, points{new Point[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.
// A program that constructs LineSegments, copies them, and assignmens them.
// Problems occur when the LineSegment objects are destructed.

#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. We delete the linesegment on the free store more than once! 
}

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 Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Vi programmerer copy constructor og assignment operator for LineSegments. Disse håndterer det underliggende dynamisk allokerede lager. Kopierer når det er nødvendigt. Programmet afslører sin opførsel på std output.

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++ Prog. Lang. (3. edition): 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();
  Point(const Point&) = default;                // Point has a default copy constructor
  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 provided for LineSegment
  ~LineSegment();                               // Destructor

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

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) : number_of_points{s}, points{new Point[s]} {
  std::cout << "LineSegment 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){    // Copy 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 4ed §17.5.1 page 507-508

Preventing object copying
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 before C++11

    • 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

Reference
  • The C++ Prog. Lang. (4. edition): Page 524, 520

  • C++11

    • It is possible to state that a function, such as a constructor, does not exit

    • It is also possible to state explicitly that a class provides the defaults

Classes and Conversion
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;     // Done with conversion operator in istream.

Implicit Conversion
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

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

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

Classes and Conversion: Examples
Slide Contents Index
References 
Intern kommentar til forelæsningen om denne slide:
Hold ørerne stive i dette eksempel. Første sæt er ganske medgørlig: Point til/fra double. Næste sæt involverer triple, og konverteringer til/fra point. Konvertering af tripple til point til double til point er mystisk - og overraskende.

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

For the exercise below

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 4.7. Conversion via constructors

In the program shown on the slide 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)?

On closer inspection, it seems wrong to copy a Point into a Tripple constructor, and the other way around. Program a variant where the relevant Point and Tripple constructors pass 'the other object' by const reference. Explain the changes you observe.

Static class members
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++ Prog. Lang. (3. edition): Page 228

Reference
  • The C++ Prog. Lang. (4. edition): Page 467-468

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 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

The programs on this slides motivate a solution on the following slide

Reference
  • The C++ Prog. Lang. (3. edition): Page 229

Reference
  • The C++ Prog. Lang. (4. edition): Page 461

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 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++ Prog. Lang. (3. edition): Page 232

Reference
  • The C++ Prog. Lang. (4. edition): Page 462-463

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

Object Self-reference
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++ Prog. Lang. (3. edition): Page 230

Reference
  • The C++ Prog. Lang. (4. edition): Page 464

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 Contents Index
References 

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

Reference
  • The C++ Prog. Lang. (3. edition): Page 235, 144, 199

Reference
  • The C++ Prog. Lang. (4. edition): Page 307, 310

  • 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 Contents Index
References 

Bjarne Stroustrup distinguishes between concrete classes and abstract classes

Reference
  • The C++ Prog. Lang. (3. edition): Page 236-242

Reference
  • The C++ Prog. Lang. (4. edition): Page 470-...

The concept concrete class: In a concrete class the representation is part of the class definition
The concept non-concrete class: In contrast, a non-concrete class can provides an interfaces to a variety of different representations

  • Concrete classes

    • Value types, as programmed with structs in C#

    • Similar to built in types

    • No superclasses - no subclasses

    • Can be allocated on the stack

  • Non-concrete classes

    • Classes with pure virtual member functions: Polymorphic types

    • Classes in class hierarchies

    • Object-oriented programming

The class Date in §16.3 in The C++ Prog. Lang. (4. edition) is an example of a concrete class

Visibility and Access Control
Slide Contents Index
References 

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

Reference
  • The C++ Prog. Lang. (4. edition): Page 600

  • 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 Contents Index
References 

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

Reference
  • The C++ Prog. Lang. (3. edition): Page 278-282

Reference
  • The C++ Prog. Lang. (4. edition): Page 571-...

  • 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 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. f can - symmetrically - access private members in 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 §19.4 in The C++ Prog. Lang. (4. edition)

Friends - Example 2
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){}

  // x.a is OK because class B is a friend of A,
  // and because class A is defined before class 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.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;                      // OK. The details of class a is known here. 
  }

  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.
// Solution to the problem in the previous example.
// 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 Contents Index
References 

We may 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 Move_relatively_1(Point&, double, double);         // We show two slightly different
  friend Point Move_relatively_2(const Point&, double, double);  // version of the Move function.

  Point(double, double);
  Point();
  double getx () const;
  double gety () const;
  void move_relatively(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_relatively(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 Move_relatively_1(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 Move_relatively_2(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_relatively(1,1);         // Mutate p, with the member function move.
  Move_relatively_1(q,1,1);       // Mutate q by the friend Move 1 - maybe misleading!
  r = Move_relatively_2(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 relative moving operation do we prefer? The move_relatively member, Move_relatively_1, or Move_relatively_2 ?

Friends - Class Point - operator friends
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

Reference

Program: Class Point - header file.
// Class Point of which operator<< is declared as a friend

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<<.
// The definition of operator<< and member functions of class Point.

#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.
// Sample use - not really interesting

#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 Contents Index
References 

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

The parameters of a friend function can, however, be implicit converted.

Reference
  • The C++ Prog. Lang. (3. edition): Page 281

Reference
  • The C++ Prog. Lang. (4. edition): Page 574

Program: Class Point - header file.
// Class Point with Move as a Friend, and with implicit conversion from a double to Point.

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<<.
// Simple implementation of class Point - not important here.

#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.
// Illustrates implicit type conversion on the targets of move and Move.

#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 the value to the left of the dot operator.
  r = Move(3,  1,2);         // Implicit conversion of 3.0 to Point(3.0).
                             // 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 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)?

Reference
  • The C++ Prog. Lang. (4. edition): Page 574

  • 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 4.8. Discuss encapsulation, visibility and accessDiscusss encapsulation, visibility and access, based on the enclosing slide.

Operator overloading
Slide Contents Index
References 

C++ supports comprehensive overloading of existing operators

Reference
  • The C++ Prog. Lang. (3. edition): Page 261-...

Reference
  • The C++ Prog. Lang. (4. edition): Page 527ff.

  • 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++ Prog. Lang. (3. edition): Page 286 - ...

Reference
  • The C++ Prog. Lang. (4. edition): Page 549ff.

Example: Operator overloading in class Point
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 - as members.

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.
// Definition of Point members, including the Point operator members. 

#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.
// Sample use of 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.
// Definition of Point member functions, including operator friends of class Point (now non-members).

#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.
// Sample use of Point operator -
// completely identical to the situation where the operators were defined as members in class Point.

#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++ Prog. Lang. (3. edition): Page 234,849
The C++ Prog. Lang. (4. edition): Page 454
Namespaces - logical program organization
The C++ Prog. Lang. (3. edition): Page 240
Effective C++, Third edition: Item 23
The C++ Prog. Lang. (3. edition): Page 226, 247, 270
The C++ Prog. Lang. (4. edition): Page 455, 501, 502
C# constructors - with inheritance
C# constructor chaining
The C++ Prog. Lang. (4. edition): Page 455
The C++ Prog. Lang. (4. edition): explicit, default, delete. Page 457, 519, 524
Rvalue references
The C++ Prog. Lang. (4. edition): Page 514
The C++ Prog. Lang. (3. edition): Page 242, 244.
The C++ Prog. Lang. (4. edition): Page 485-489
The C++ Prog. Lang. (3. edition): Page 364-367
The C++ Prog. Lang. (4. edition): Page 64, 112, 356
The C++ Prog. Lang. (4. edition): Page 112-114, 987-993
Effective Modern C++: unique_ptr. Item 18
Effective Modern C++: shared_ptr. Item 19
The C++ Prog. Lang. (3. edition): Page 245-246
Effective C++, Third edition: Item 10
Effective C++, Third edition: Item 6
The C++ Prog. Lang. (4. edition): Page 524, 520
Type conversion in general
The C++ Prog. Lang. (3. edition): Page 228
The C++ Prog. Lang. (4. edition): Page 467-468
Effective C++, Third edition: Item 4, and 18 (page 80)
The C++ Prog. Lang. (3. edition): Page 229
The C++ Prog. Lang. (4. edition): Page 461
Effective C++, Third edition: Item 3
The C++ Prog. Lang. (3. edition): Page 232
The C++ Prog. Lang. (4. edition): Page 462-463
The C++ Prog. Lang. (3. edition): Page 230
The C++ Prog. Lang. (4. edition): Page 464
The C++ Prog. Lang. (3. edition): Page 235, 144, 199
The C++ Prog. Lang. (4. edition): Page 307, 310
The C++ Prog. Lang. (3. edition): Page 236-242
The C++ Prog. Lang. (4. edition): Page 470-...
The C++ Prog. Lang. (4. edition): Page 600
The C++ Prog. Lang. (3. edition): Page 278-282
The C++ Prog. Lang. (4. edition): Page 571-...
Non-friend operator<< in class Point
The C++ Prog. Lang. (3. edition): Page 281
The C++ Prog. Lang. (4. edition): Page 574
The C++ Prog. Lang. (3. edition): Page 261-...
The C++ Prog. Lang. (4. edition): Page 527ff.
The C++ Prog. Lang. (3. edition): Page 286 - ...
The C++ Prog. Lang. (4. edition): Page 549ff.

 

Chapter 4: 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: August 1, 2017, 13:28:38