Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Abstraction Mechanisms, Part 1


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

Solution

Here is my version of the program:

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

using namespace std;

void f(){

  Point *p = new Point(),
        *q = new Point(),
        *r = new Point(11.0, 12.0),
        *s = new Point(*p),                      
        *t = new Point(3.0),           
        *u = new Point(*t),
        *ap[10]; 

  for(int i = 0; i < 10; i++)
     ap[i] = new Point();

  *q = *r;

  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;  

  delete p; delete q; delete r; delete s; delete t; delete u;
  for(int i = 0; i < 10; i++) delete ap[i];

}

int main(){
  f();
}

 


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

Solution

Here is a version with comments and explanations:

#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;   // (0,7)

  cout << "pf(q): " << pf(p) << endl; // (0,7)
                                      // Passing p via const L-value reference (0,7)  - no copy constructor nor rvalue constructor.
                                      // Returning a const reference: (0,7)

  Point q = pf(p);                    // Passing p via const L-value reference (0,7)  - no copy constructor nor rvalue constructor.
                                      // Returning an L-value reference (0,7). Then copying it to q via copy constructor (!!). 
  cout << "Point q: " << q << endl;   // (1,9) 

  Point r = pf(Point{10,12});         // Passing Point{10,12} as above  - no copy constructor nor rvalue constructor.
                                      // Returning an L-value reference (10,12). Copying it to r via copy constructor.
  cout << "Point r: " << r << endl;   // (11,14)

}

int main(){
  f();
}


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

Solution

The two problems are

  1. When r is copied to s both r and s refer to the same point representation (because the pointer is copied in the Point copy constructor.
  2. When s goes out of scope, the Point destructor is called. Hereby delete is called on the memory, which already has been deleted because also r goes out of scope.

Here is an improved version of the implementation of class Point. Notice the improved copy constructor. In this version, it copies the point representation.

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


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

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

Point::Point(Point& p){   // Better copy constructor
  point_representation = new double[2];
  point_representation[0] = p.getx();
  point_representation[1] = p.gety();
}

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

The client program is here (only comments have been changed):

#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);
  cout << "Point s: "      << s << endl << endl; // (11,12)

  r.move(1.0, 2.0);

  cout << "Point s: "      << s << endl;         // (11,12)   
  cout << "Point r: "      << r << endl << endl; // (12,14)

  // Now f returns, and the program runs as expected. The reason is that
  // s now refers to a copy of the representation of r. Thus, both
  // r and s can be safely deleted when they go out of scope (when f returns to main).
}

int main(){
  f();
}

 


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

Solution

Here is my solution:

// Variation of unique-ptr-1.cc

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

using namespace std;

unique_ptr<Point> f(unique_ptr<Point> pp1){   // Notice the new signature.
  unique_ptr<Point>ap1(move(pp1)),      // Cannot copy unique_ptr. Cast to rvalue by move.  
                   ap2(new Point(3,4));

  cout << "ap1: " << *ap1 << endl; 
  cout << "ap2: " << *ap2 << endl; 

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

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

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

  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;

  return ap2;        // ap2 is moved out of f.
}

int main(){
  unique_ptr<Point> p(new Point(1,2));
  p = f(move(p));   // notice move

  cout << "p: " << *p << endl; // Now OK. (1,2).
                               // p is moved back from f.
}

The program output is:

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)
p: (1,2)
Point deleted

Notice that we manage to move the point (1,2) back to main, and that this version of the program does not fail, as did the original program just before the end of main.


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

Solution

Here is my solution:

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

using namespace std;

void f(shared_ptr<Point> pp1){
  shared_ptr<Point>ap1(pp1),               // Now two references to (1,2)
                   ap2(new Point(3,4));

  cout << "ap1: " << *ap1 << endl;   
  cout << "ap2: " << *ap2 << endl;   

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

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

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

  ap2 = ap1;                          // Now three reference to (1,2).
                                      // ap2 (3,4) is deleted here (the one and only reference is gone!)
                                     
  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.
  // But there is still a reference to (1,2) - from the copy of the shared pointer in main.
}

int main(){
  shared_ptr<Point>p(new Point(1,2));
  f(p);

  // (1,2) is still alive here!

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

The program output is:

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)
p: (1,2)
Point deleted


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

Solution

Here are the revised program parts. First the header file:

// New example: Converting between Point an Tripple...

class Tripple;                 // NECESSARY FORWARD DECLARATION OF CLASS 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) 
  Point(Tripple t);             // (t.a, t.b)   NEW, INSTEAD OF CONVERSION IN Tripple.

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

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

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

public:
  friend class Point;           // GIVES POINT ACCESS TO PRIVATE a, b, and c.
                                // This is convenient in the new constructor Point::Point(Tripple) 
  Tripple();                    // (0, 0, 0)
  Tripple(int x, int y, int z); // (x, y, z)
  Tripple(Point p);             // (p.x, p.y, 0)

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


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

The cpp file:

#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){
}

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

Point::Point(Tripple t): x(t.a), y (t.b){     // NEW CONSTRUCTOR
}                                             // Can access a and b in t because Point 
                                              // has granted friendship to Tripple.

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


The client program:

#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;           // p2 is copied into the tripple constructor as (1,9). The tripple constructor makes (1,9,0).

  p1 = t1;           // (1,9,0) is default copied into the constructor Point(Tripple), as (1,9,0), which makes the point (1,9).

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

When I run this program I get this output:

t1: [0,0,0]
t1: [1,9,0]
p1: (1,9)

As it appears, we get natural and expected results. Thus, it turns out to be easier to control the conversions in this variation of the program

When we, finally, change the Point and Tripple constructors, we avoid the unfortunate activation of the Point constructor during the conversion. We get natural results.


4.7   Discuss encapsulation, visibility and access  

Discusss encapsulation, visibility and access, based on the enclosing slide.


Generated: Tuesday August 1, 2017, 13:29:24