DM560, Introduction to Programming in C++

Sheet 10

Compile these files with the flags: -fsanitize=address -fsanitize=leak to check that memory is handled properly.

For example:

g++ -Wfatal-errors -fsanitize=address -fsanitize=leak ex11_709.cpp

Task 4

Exercise 10 of Chapter 19 on page 709.

Implement a simple unique_ptr supporting only a constructor, destructor, –>, *, and release(). In particular, don’t try to implement an assignment or a copy constructor.

// compile with flags: -Wfatal-errors -fsanitize=address -fsanitize=leak
#include<iostream>
#include<vector>
#include<memory>

using namespace std;

class MyException {};

// Begin: this is what the exercise asks 
template<class T> class unique_pointer {
public:
  typedef T*               pointer;
  typedef T                element_type;      
private:
  pointer p;
  element_type e;
public:
  // Constructors.
  unique_pointer() { p=nullptr; };
  explicit unique_pointer(pointer _p) : p(_p){};

  ~unique_pointer() {
    cout<<"delete"<<endl;
    delete p;
  };

  pointer get() const { return p; }
  
  T& operator*() const
  {
    return *get();
  }
  
  pointer operator->() const
  {
    return get();
  }

  pointer release() 
  {
    pointer _p = get();
    p = nullptr;
    return _p;
  }
   
};
// End: this is what the exercise asks



// This is a specialization: a different implementation for a template when a specific type is passed as template
// parameter
template<class T> class unique_pointer<T[]> {
public:
  typedef T*               pointer;
  typedef T                element_type;      
private:
  pointer p;
  element_type e;
public:
  // Constructors.
  unique_pointer() { p=nullptr; };
  explicit unique_pointer(pointer _p) : p(_p){};

  ~unique_pointer() {
    cout<<"delete []"<<endl;
    delete [] p;
  }

  pointer get() const { return p; }


  T&  operator[](size_t __i) const 
  {
    return get()[__i];
  }
  
  T& operator*() const
  {
    return *get();
  }
  
  pointer operator->() const
  {
    return get();
  }

  pointer release() 
  {
    pointer _p = get();
    p = nullptr;
    return _p;
  }
   
};







vector<int>* make_vec1()
{
  unique_pointer<vector<int>> p {new vector<int>}; // allocate on free store

  // do something with the container
  for (int i=0; i<10; i++)
    p->push_back(i);

  if (p->size()>=10) throw MyException{};
  // if an exception is thrown while the vector<int> is being filled, or if we return prematurely
  // from make_vec , the vector<int> is properly destroyed. The p.release() extracts
  // the contained pointer (to the vector<int> ) from p so that we can return it, and it
  // also makes p hold the nullptr so that destroying p (as is done by the return ) does
  // not destroy anything.
  
  return p.release();
};


unique_pointer<vector<int>> make_vec2()
{
  unique_pointer<vector<int>> p(new vector<int>); // allocate on free store

  // do something with the container
  if (true) throw MyException{};

  return p;
};



int* make_array1()
{
  unique_pointer<int[]> p(new int[10]); // allocate on free store

  // do something with the container
  for (int i=0; i<10; i++)
    p[i]=i;
  if (true) throw MyException{};
 
  return p.release();
};






int main (int argc, char **argv) {

  try {
    vector<int> *pv;
    pv=make_vec1();
    // no need to delete, the destructor of vector<int> is called when the pointer goes out of scope.
  } catch (MyException) {
    cerr<<"The object the size of 10 and an exception was thrown before it could be deallocated\n";
    cerr<<"but it was correctly deallocated by destructor\n";
  }


  try {
    int *pa;
    pa=make_array1();
    delete [] pa;
  } catch (MyException) {
    cerr<<"The object the size of 10 and an exception was thrown before it could be deallocated\n";
    cerr<<"but it was correctly deallocated by destructor\n";
  }
  

  try {
    unique_pointer<vector<int>> pv;
    pv=make_vec2();
    // unique_pointer destructor will handle the deallocation of pv;
  } catch (MyException) {
    cerr<<"The object the size of 10 and an exception was thrown before it could be deallocated\n";
    cerr<<"but it was correctly deallocated by destructor\n";
  }


  exit(EXIT_SUCCESS);
}

Task 5

Exercise 11 of Chapter 19 on page 709.

Design and implement a counted_ptr<T> that is a type that holds a pointer to an object of type T and a pointer to a “use count” (an int) shared by all counted pointers to the same object of type T. The use count should hold the number of counted pointers pointing to a given T. Let the counted_ptr’s constructor allocate a T object and a use count on the free store. Let counted_ptr’s constructor take an argument to be used as the initial value of the T elements. When the last counted_ptr for a T is destroyed, counted_ptr’s destructor should delete the T. Give the counted_ptr operations that allow us to use it as a pointer. This is an example of a “smart pointer” used to ensure that an object doesn’t get destroyed until after its last user has stopped using it. Write a set of test cases for counted_ptr using it as an argument in calls, container elements, etc.

// compile with flags: -Wfatal-errors -fsanitize=address -fsanitize=leak
#include<iostream>
#include<vector>


using namespace std;


template<class T> class counted_pointer {
public:
  typedef T*               pointer;
  typedef T                element_type;      
private:
  pointer p;
  element_type e;
  int *use_count;
public:
  // Constructors.
  counted_pointer() { p=nullptr; }
  explicit counted_pointer(pointer _p) : p(_p) {use_count=new int(1);}

  ~counted_pointer() {
    cout<<*use_count<<endl<<flush;
    (*use_count)--;
    if (*use_count==0)
    {
      cout<<"delete"<<endl;
      delete p;
    }
  }

  int* get_counter() {
    return use_count;
  }
  
  counted_pointer& operator=(counted_pointer &u)
  {
    use_count=u.get_counter();
    (*use_count)++;
    cout<<*use_count<<endl;
    p=u.get();
    return *this;
  }

  pointer get() { return p; }
  pointer get() const { return p; }
  
  T& operator*() const
  {
    return *get();
  }
  
  pointer operator->() const
  {
    return get();
  }

  pointer release() 
  {
    pointer _p = get();
    *use_count--;
    p = nullptr;
    return _p;
  }
   
};








int main (int argc, char **argv) {
  //counted_pointer<vector<int>> p;
  counted_pointer<vector<int>> p1(new vector<int>);
  p1->push_back(2);
  p1->push_back(3);
   
  {
    counted_pointer<vector<int>> p2;

    p2=p1;
    //p2 goes out of scope but it does not delete the object
  }
  
  // now p1 goes out of scope and since the *use_count is just one it deletes the object.
  exit(EXIT_SUCCESS);
}