Exercise index of this lecture   Alphabetic index   Course home   

Exercises
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


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


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


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


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


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


4.7   Discuss encapsulation, visibility and access  

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


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