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();
int size() const;
double& at(int i);
double at(int i) const;
double& operator[](int i);
double operator[](int i) const;
void push_back(double d);
void pop_back();
private:
int _size;
int _capacity;
double* _ptr;
};
#endif
#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();
DVector(const DVector& rhs);
DVector& operator=(const DVector& rhs);
~DVector();
int size() const;
double& at(int i);
double at(int i) const;
double& operator[](int i);
double operator[](int i) const;
void push_back(double d);
void pop_back();
private:
int _size;
int _capacity;
double* _ptr;
};
#endif
#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 )
{
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;
}
DVector::~DVector()
{
delete [] _ptr;
}
We usually follow DRY (== Don't Repeat Yourself)
#ifndef DVECTOR_H
#define DVECTOR_H
class DVector
{
public:
DVector();
DVector(const DVector& rhs);
DVector& operator=(const DVector& rhs);
~DVector();
int size() const;
double& at(int i);
double at(int i) const;
double& operator[](int i);
double operator[](int i) const;
void push_back(double d);
void pop_back();
private:
int _size;
int _capacity;
double* _ptr;
void copy(const DVector& rhs);
void release();
};
#endif
#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 )
{
release();
copy(rhs);
}
return *this;
}
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;
}
$ ./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();
DVector(const DVector& rhs);
DVector& operator=(const DVector& rhs);
~DVector();
int size() const;
double& at(int i);
double at(int i) const;
double& operator[](int i);
double operator[](int i) const;
void push_back(double d);
void pop_back();
private:
int _size;
int _capacity;
double* _ptr;
void copy(const DVector& rhs);
void release();
void grow();
};
#endif
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();
DVector(const DVector& rhs);
DVector& operator=(const DVector& rhs);
~DVector();
int size() const;
double& at(int i);
double at(int i) const;
double& operator[](int i);
double operator[](int i) const;
void push_back(double d);
void pop_back();
private:
int _size;
int _capacity;
double* _ptr;
void copy(const DVector& rhs);
void release();
void grow();
};
#endif
#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 )
{
release();
copy(rhs);
}
return *this;
}
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;
}