A while ago, I found in a website some code examples of utility functions that are used when creating, destructing objects, or even when overloading some of their operators.
More precisely, the following member functions are mainly used: init, copy, set, and destroy.
- The
initmember function is used to initialize all the private members. It’s mostly called inside theconstructors, e.g. thedefaultorparameter constructor. - The
copymember function is used to do adeep copyof an object passed as aconst reference. It is called inside thereference constructorand the overload of theoperator =. - The
setmember function which mainlyallocatesmemory for theprivate membersthat require it. - Finally, the
destroymember function is used forreleasingthe allocated memory. It’s called, for example, inside of thedestructor.
I would like to have your opinion and know if this is a good programming practice? Which are the benefits or drawbacks? Any comments and suggestions are welcomed!
Below, I’m illustrating how those member functions are defined for a CMatrix<T> class.
matrix.h
template < class T >
class CMatrix{
CMatrix(){ this->initMatrix(); }
CMatrix(int nRows, int nCols, int nChannels){
this->initComplexMatrix();
this->setComplexMatrix(nRows, nCols, nChannels);
}
CMatrix(const CMatrix<T> & refMatrix){
this->initComplexMatrix();
this->copyComplexMatrix(refMatrix);
}
CMatrix<T> & operator = (const CMatrix<T> & refMatrix){
if(this!=&refMatrix){
this->destroyComplexMatrix();
this->initComplexMatrix();
this->copyComplexMatrix(refMatrix);
}
return (*this);
}
T & CMatrix<T>::operator()(int, int, int);
T CMatrix<T>::operator()(int, int, int) const;
......
void initMatrix();
void copyMatrix(const CMatrix<T> & );
void setMatrix(int, int, int = 1);
void destroyMatrix();
......
~CMatrix(){ this->destroyMatrix(); }
private:
T *** m_pData;
int m_nRows;
int m_nCols;
int m_nChannels;
};
matrix.cpp
#include <matrix.h>
template < class T >
inline T & CMatrix<T>::operator()(int mrow, int mcol, int mchannel){
assert(mrow >= 0 && mrow < this->getRows());
assert(mcol >= 0 && mcol < this->getCols());
assert(mchannel >= 0 && mchannel < this->getChannels());
return this->m_pData[mrow][mcol][mchannel];
}
template < class T >
void CMatrix<T>::initMatrix(){
this->m_nRows = 0;
this->m_nCols = 0;
this->m_nChannels= 0;
this->m_pData = NULL;
}
template < class T >
void CMatrix<T>::copyMatrix(const CMatrix<T> & refMatrix){
if(refMatrix.m_pData!=NULL){
this->setMatrix(refMatrix.getRows(), refMatrix.getCols(), refMatrix.getChannels());
for(register int dy=0; dy < this->getRows(); dy++){
for(register int dx=0; dx < this->getCols(); dx++){
for(register int ch=0; ch < this->getChannels(); ch++){
this->m_pData[(dy)][(dx)][(ch)] = refMatrix.m_pData[(dy)][(dx)][(ch)];
}
}
}
}
else{
this->m_pData = NULL;
}
}
template < class T >
void CMatrix<T>::setMatrix(int nRows, int nCols, int nChannels){
this->destroyMatrix();
this->m_pData = NULL;
this->m_pData = new T ** [nRows];
for(register int dy=0; dy < nRows; dy++){
this->m_pData[dy] = NULL;
this->m_pData[dy] = new T * [nCols];
for(register int dx=0; dx < nCols; dx++){
this->m_pData[dy][dx] = NULL;
this->m_pData[dy][dx] = new T[nChannels];
}
}
this->setRows(mrows);
this->setCols(mcols);
this->setChannels(mchannels);
}
template < class T >
void CMatrix<T>::destroyMatrix(){
if(this->m_pData!=NULL){
for(register int dy=0; dy < this->getRows(); dy++){
for(register int dx=0; dx < this->getCols(); dx++){
delete [] this->m_pData[dy][dx];
}
delete [] this->m_pData[dy];
}
delete [] this->m_pData;
this->m_pData = NULL;
}
}
No, this is not recommended. The way you propose is not exception safe and is not compatible with
constor subobjects that need non-default construction.Instead use the ctor-initializer-list. Code reuse can be achieved through static helper functions called in the ctor-initializer-list or by moving logic into subobject constructors.
For memory allocation, use a subobject per resource. The memory management logic ends up in the subobject constructor and destructor. In many cases, you can use existing RAII classes from the library, such as
std::vector, and not need to write any memory management code yourself.Most operators can reuse the logic in the constructors, using the copy-and-swap idiom.
EDIT: Exception-safe construction might look something like this:
The
std::vectordestructor will take care of freeing the memory, and since the two allocations are separate objects, ifm_cellsfails to allocate its memory, the destructor form_rowswill run and nothing will leak.Of course,
std::vectoris a little bit overkill here, a fixed-size array RAII class would be sufficient. Butstd::auto_ptrcan’t be used with arrays. I think C++0x is supposed to add a standard fixed-size RAII array class.EDIT: Per request, a 3-D version: