POD and non-POD classes
=======================



C++ uses "value-semantic". Objects are copied by default.
(From C++11 we have move semantics, but that is a special case).


        x = y;


will overwrite the bytes of "x" with the bytes of "y".


This is trivial for built-in types. But how structs/classes behave?:
(In Java, no copy happens. In C# struct copies, class not)


struct MyStruct
{
  int       i;
  double    d;
  MyStruct *ptr;
};


MyStruct  x, y;

    x.i   = 1;
    x.d   = 3.14;
    x.ptr = new MyStruct();


    y = x;

For struct/class types the compiler generates memberwise copy,
i.e. all data members are copied by their assignment operators.


    y.i   = x.i;
    y.d   = x.d;
    y.ptr = x.ptr



(Remark: no default operator== or operator!= are generated).



  ____________
  |    1     | x
  |   3.14   |             ______________
  |    ------------------> |            |
  |__________|           / |            |
                        /  |            |
                       /   |____________|
                      /
  ____________       /
  |    1     | y    /
  |   3.14   |     /
  |    ------------
  |__________|





This memberwise copy not always good for us.



Example: try to create std::vector-like class.


  ____________
  |   size   |
  | capacity |           ____________________________
  |   ptr ------------> |_______________|____________|
  |__________|                        _size       _capacity








#ifndef DVECTOR_H
#define DVECTOR_H


class DVector
{
public:
  DVector();    // constructor

  int     size() const;    // actual size 

  double& at(int i);       // i-th element
  double  at(int i) const; // i-th element, const member

  double& operator[](int i);        // unchecked access
  double  operator[](int i) const;  // unchecked access, const member

  void    push_back(double d);  // append to end
  void    pop_back();           // remove from end;

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

};


#endif /* DVECTOR_H */







// dvector.cpp

#include <stdexcept>
#include "dvector.h"


DVector::DVector()
{
  _capacity = 64;
  _size = 0;
  _ptr = new double[_capacity];
}

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

double& DVector::at(int i)
{
  if ( i >= _size )
    throw std::out_of_range("bad index");

  return _ptr[i];
}

double DVector::at(int i) const
{
  if ( i >= _size )
    throw std::out_of_range("bad index");

  return _ptr[i];
}

double& DVector::operator[](int i)
{
  return _ptr[i];
}

double DVector::operator[](int i) const
{
  return _ptr[i];
}

void DVector::push_back(double d)
{
  if ( _size == _capacity )
    throw std::out_of_range("vector full");

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


void DVector::pop_back()
{
  if ( 0 == _size )
    throw std::out_of_range("vector empty");

  --_size;
}








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


int main()
{
  DVector x;

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

  DVector y;

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

  std::cout << "x = [ ";
  for (int i = 0; i < x.size(); ++i )
    std::cout << x.at(i) << " ";
  std::cout << "]" << std::endl;

  std::cout << "y = [ ";
  for (int i = 0; i < y.size(); ++i )
    std::cout << y.at(i) << " ";
  std::cout << "]" << std::endl;

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

  std::cout << "x = [ ";
  for (int i = 0; i < x.size(); ++i )
    std::cout << x[i] << " ";
  std::cout << "]" << std::endl;

  std::cout << "y = [ ";
  for (int i = 0; i < y.size(); ++i )
    std::cout << y[i] << " ";
  std::cout << "]" << std::endl;

  x[0] = 999;
  std::cout << "x[0] = 999" << std::endl;

  std::cout << "x = [ ";
  for (int i = 0; i < x.size(); ++i )
    std::cout << x[i] << " ";
  std::cout << "]" << std::endl;

  std::cout << "y = [ ";
  for (int i = 0; i < y.size(); ++i )
    std::cout << y[i] << " ";
  std::cout << "]" << std::endl;

  return 0;
}




$ ./a.out
x = [ 0 1 2 3 4 5 6 7 8 9 ]
y = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x = y
x = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
y = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x[0] = 999
x = [ 999 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
y = [ 999 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]




What is the problem?:

DVector is non-POD! For non-POD classes we (usually)
have to manually write

- copy constructor
- copy assignment
- destructor


This is the "rule of three"



(In C++11 we have rule of five for move operations)




This happened on x = y:


  ____________
  |  _size   | x
  |_capacity |              ______________________________
  |  _ptr ---------------> |______________________________|
  |__________|           /
                        /
                       /
                      /
  ____________       /
  |  _size   | y    /
  |_capacity |     /
  |  _ptr ---------
  |__________|






We need this:



  ____________
  |  _size   | x
  |_capacity |              ______________________________
  |  _ptr ---------------> |______________________________|
  |__________|
                             |  |  |       |
                             |  |  |       |
                             |  |  |  ...  |
  ____________               |  |  |       |
  |  _size   | y             V  V  V       V
  |_capacity |              ______________________________
  |  _ptr ---------------> |______________________________|
  |__________|






#ifndef DVECTOR_H
#define DVECTOR_H


class DVector
{
public:
  DVector();    // constructor

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

  ~DVector();   // destructor

  int     size() const;    // actual size 

  double& at(int i);       // i-th element
  double  at(int i) const; // i-th element, const member

  double& operator[](int i);        // unchecked access
  double  operator[](int i) const;  // unchecked access, const member

  void    push_back(double d);  // append to end
  void    pop_back();           // remove from end;

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

};


#endif /* DVECTOR_H */




// dvector.cpp

#include <stdexcept>
#include "dvector.h"


DVector::DVector()
{
  _capacity = 64;
  _size = 0;
  _ptr = new double[_capacity];
}

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

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

DVector& DVector::operator=(const DVector& rhs)
{
  if ( this != &rhs )  // avoid x = x
  {
    delete [] _ptr;

    _capacity = rhs._capacity;
    _size = rhs._size;
    _ptr = new double[_capacity];

    for (int i = 0; i < _size; ++i)
      _ptr[i] = rhs._ptr[i];
  }
  return *this;  // for x = y = z
}

DVector::~DVector()
{
  delete [] _ptr;
}

//... rest of dvector.cpp is the same





We usually follow DRY (== Don't Repeat Yourself)






#ifndef DVECTOR_H
#define DVECTOR_H


class DVector
{
public:
  DVector();    // constructor

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

  ~DVector();   // destructor

  int     size() const;    // actual size 

  double& at(int i);       // i-th element
  double  at(int i) const; // i-th element, const member

  double& operator[](int i);        // unchecked access
  double  operator[](int i) const;  // unchecked access, const member

  void    push_back(double d);  // append to end
  void    pop_back();           // remove from end;

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

  void copy(const DVector& rhs);   // private helper function
  void release();                  // private helper function

};


#endif /* DVECTOR_H */




#include <stdexcept>
#include "dvector.h"


DVector::DVector()
{
  _capacity = 64;
  _size = 0;
  _ptr = new double[_capacity];
}

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

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

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

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

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

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

//... rest of dvector.cpp is the same



$ ./a.out
x = [ 0 1 2 3 4 5 6 7 8 9 ]
y = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x = y
x = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
y = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
x[0] = 999
x = [ 999 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]
y = [ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ]






Last issue: our vector has limited capacity. Make it unlimited:



#ifndef DVECTOR_H
#define DVECTOR_H


class DVector
{
public:
  DVector();    // constructor

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

  ~DVector();   // destructor

  int     size() const;    // actual size 

  double& at(int i);       // i-th element
  double  at(int i) const; // i-th element, const member

  double& operator[](int i);        // unchecked access
  double  operator[](int i) const;  // unchecked access, const member

  void    push_back(double d);  // append to end
  void    pop_back();           // remove from end;

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

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


#endif /* DVECTOR_H */





// modifications in dvector.cpp

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


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

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

  delete [] _oldptr;
}


void DVector::push_back(double d)
{
  if ( _size == _capacity )
    grow();

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







Final code:




#ifndef DVECTOR_H
#define DVECTOR_H


class DVector
{
public:
  DVector();    // constructor

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

  ~DVector();   // destructor

  int     size() const;    // actual size 

  double& at(int i);       // i-th element
  double  at(int i) const; // i-th element, const member

  double& operator[](int i);        // unchecked access
  double  operator[](int i) const;  // unchecked access, const member

  void    push_back(double d);  // append to end
  void    pop_back();           // remove from end;

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

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


#endif /* DVECTOR_H */






#include <stdexcept>
#include "dvector.h"


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

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

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

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

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

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

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


void DVector::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 DVector::size() const
{
  return _size;
}

double& DVector::at(int i)
{
  if ( i >= _size )
    throw std::out_of_range("bad index");

  return _ptr[i];
}

double DVector::at(int i) const
{
  if ( i >= _size )
    throw std::out_of_range("bad index");

  return _ptr[i];
}

double& DVector::operator[](int i)
{
  return _ptr[i];
}

double DVector::operator[](int i) const
{
  return _ptr[i];
}

void DVector::push_back(double d)
{
  if ( _size == _capacity )
    grow();

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


void DVector::pop_back()
{
  if ( 0 == _size )
    throw std::out_of_range("vector empty");

  --_size;
}