The DStack example
==================



//
// dstack.h
//

#ifndef DSTACK_H
#define DSTACK_H

#include <iosfwd>  // only declarations of <iostream> : shorter, cheaper

class DStack
{
  friend void print(std::ostream&, const DStack&); // give access rights 

public:
  DStack();     // constructor

  DStack(const DStack& rhs);            // copy constructor
  DStack& operator=(const DStack& rhs); // assignment operator

  ~DStack();    // destructor

  int     size() const;    // actual size 
  bool    empty() const;   // size() == 0

  double  top() const;     // read top element
  double& top();           // access top element

  void    push(double d);  // append to end
  void    pop();           // remove from end;

private:
  int     _size;        // actual number of elements
  int     _capacity;    // buffer size
  double* _ptr;         // pointer to buffer

  void copy(const DStack& rhs);    // private helper function
  void release();                  // private helper function
  void grow();                     // reallocate buffer
};

void print(std::ostream& os, const DStack& ds); // print DStack for debug

#endif /* DSTACK_H */



Friends:
========

Normally , private data members and memberfunctions are accessible
only by memberfunctions. Friend declarations make friend functions
access to private part.

Friend class declaration gives access right to all memberfunctions
of that class.

Use friends with care as an exception.



//
//
#include <stdexcept>
#include <iostream>
#include "dstack.h"


DStack::DStack()
{
  _capacity = 4;
  _size = 0;
  _ptr = new double[_capacity];
}

DStack::DStack(const DStack& rhs)
{
  copy(rhs);
}

DStack& DStack::operator=(const DStack& rhs)
{
  if ( this != &rhs )  // avoid x = x
  {
    release();
    copy(rhs);
  }
  return *this;  // for x = y = z
}

DStack::~DStack()
{
  release();
}

void DStack::copy(const DStack& rhs)
{
  _capacity = rhs._capacity;
  _size = rhs._size;
  _ptr = new double[_capacity];

  for (int i = 0; i < _size; ++i)
    _ptr[i] = rhs._ptr[i];
}

void DStack::release()
{
  delete [] _ptr;
}


void DStack::grow()
{
  double *_oldptr = _ptr;
  _capacity = 2 * _capacity;
  _ptr = new double[_capacity];

  for ( int i = 0; i < _size; ++i)
    _ptr[i] = _oldptr[i];

  delete [] _oldptr;
}

int DStack::size() const
{
  return _size;
}

bool DStack::empty() const
{
  return 0 == _size;
}

void DStack::push(double d)
{
  if ( _size == _capacity )
    grow();

  _ptr[_size] = d;
  ++_size;
}

void DStack::pop()
{
  if ( 0 == _size )
    throw std::out_of_range("stack empty");

  --_size;
}

double DStack::top() const   // this is to read the top element
{
  if ( 0 == _size )
    throw std::out_of_range("stack empty");

  return _ptr[_size-1];
}

double& DStack::top()   // this is to access (write) the top element    
{
  if ( 0 == _size )
    throw std::out_of_range("stack empty");

  return _ptr[_size-1];
}

void print(std::ostream& os, const DStack& ds)
{
  os << "[ ";
  for (int i = 0; i < ds._size; ++i)
    os << ds._ptr[i] << " ";
  os << "]" << std::endl;
}



And here is the test program:



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


int main()
{
  DStack x;

  for (int i = 0; i < 10; ++i )
    x.push(i);

  DStack y;

  for (int i = 0; i < 15; ++i )
    y.push(i+20);

  print( std::cout, x);
  print( std::cout, y);

  x = y;
  std::cout << "x = y" << std::endl;

  print( std::cout, x);
  print( std::cout, y);

  x.top() = 999;
  std::cout << "x.top() = 999" << std::endl;

  print( std::cout, x);
  print( std::cout, y);

  std::cout << "stress test for memory leaks..." << std::endl;
  for(int i = 0; i < 1000; ++i) // set to 100000 for real test
  {
    std::cout << i << " ";
    for(int j = 0; j < 1000; ++j) // set to 100000 for real test
    {
      DStack z1, z2;
      z1.push(j); z2.push(j);
      z1 = z2;
    }
  }
  std::cout << "  ... ok" << std::endl;

  return 0;
}



$ g++ -ansi -pedantic -Wall -W dstack.cpp dstackmain.cpp

$ ./a.out
[ 0 1 2 3 4 5 6 7 8 9 ]
[ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x = y
[ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
[ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x.top() = 999
[ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 999 ]
[ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
stress test for memory leaks...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1
...
988 989 990 991 992 993 994 995 996 997 998 999   ... ok




But we can do it better, reusing DVector:





#ifndef DSTACK_H
#define DSTACK_H

#include <iosfwd>     // only declarations of <iostream>
#include "dvector.h"

class DStack
{
  friend void print(std::ostream&, const DStack&); // give access rights 

public:
  // no need for special member functions, 
  // DVector works as it is

  int     size() const;    // actual size 
  bool    empty() const;   // size() == 0

  double  top() const;     // read top element
  double& top();           // access top element

  void    push(double d);  // append to end
  void    pop();           // remove from end;

private:
  DVector _dvec;   // reuse DVector
};

void print(std::ostream& os, const DStack& ds); // print DStack for debug

#endif /* DSTACK_H */




We do not define special memberfunctions for DStack:

- default constructor
- copy constructor
- assignment operator=
- destructor

will be automatically created, and call memberwise operations on data,
which is the only DVector member.

Since we created the special memberfunctions for DVector, they will be used.





#include <stdexcept>
#include <iostream>
#include "dstack.h"

int DStack::size() const
{
  return _dvec.size();
}

bool DStack::empty() const
{
  return 0 == _dvec.size();
}

void DStack::push(double d)
{
  _dvec.push_back(d);
}

void DStack::pop()
{
  if ( 0 == _dvec.size() )
    throw std::out_of_range("stack empty");  // instead of "vector empty"

  _dvec.pop_back();
}

double DStack::top() const
{
  if ( 0 == _dvec.size() )
    throw std::out_of_range("stack empty");

  return _dvec[_dvec.size()-1];
}

double& DStack::top()
{
  if ( 0 == _dvec.size() )
    throw std::out_of_range("stack empty");

  return _dvec[_dvec.size()-1];
}

void print(std::ostream& os, const DStack& ds)
{
  os << "[ ";
  for (int i = 0; i < ds._dvec.size(); ++i)
    os << ds._dvec[i] << " ";
  os << "]" << std::endl;
}



No change in the test program.
We have to link dvector to the program also.


$ g++ -ansi -pedantic -Wall -W dstack.cpp dstackmain.cpp dvector.cpp