Служебные функции для конструкторов классов, деструкторов и перегрузки операторов - PullRequest
1 голос
/ 17 февраля 2011

Некоторое время назад я обнаружил на веб-сайте несколько примеров кода служебных функций, которые используются при creating, destructing объектах или даже при перегрузке некоторых их operators. Точнее, в основном используются следующие функции-члены: init, copy, set и destroy.

  • Функция-член init используется для инициализации всех закрытых членов. В основном он вызывается внутри constructors, например default или parameter constructor.
  • Функция-член copy используется для deep copy объекта, переданного как const reference. Он вызывается внутри reference constructor и перегрузки operator =.
  • Функция-член set, которая в основном allocates память для private members, которые в ней нуждаются.
  • Наконец, destroy функция-член используется для releasing выделенной памяти. Это называется, например, внутри destructor.

Мне бы хотелось узнать ваше мнение и узнать, является ли это хорошей практикой программирования? Каковы преимущества или недостатки? Любые комментарии и предложения приветствуются! Ниже я иллюстрирую, как эти функции-члены определены для CMatrix<T> класса.

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;
    } 
}

Ответы [ 2 ]

5 голосов
/ 17 февраля 2011

Нет, это не рекомендуется.То, как вы предлагаете, не является исключительной безопасностью и несовместимо с const или подобъектами, которые требуют построения не по умолчанию.

Вместо этого используйте ctor-initializer-list .Повторное использование кода может быть достигнуто с помощью статических вспомогательных функций, вызываемых в ctor-initializer-list , или путем перемещения логики в конструкторы подобъектов.

Для выделения памяти используйте подобъект на ресурс.Логика управления памятью заканчивается в конструкторе и деструкторе подобъекта.Во многих случаях вы можете использовать существующие классы RAII из библиотеки, такие как std::vector, и вам не нужно самостоятельно писать какой-либо код управления памятью.

Большинство операторов могут повторно использовать логику в конструкторах, используя идиома копирования и замены .

РЕДАКТИРОВАТЬ: Конструкция, исключающая исключительные ситуации, может выглядеть примерно так:

#include <vector>
template<typename T>
class matrix
{
    int m_nCols;
    std::vector<T*> m_rows;
    std::vector<T> m_cells;
    size_t makeIndex( int row, int col ) const { return row*m_nCols + col; }

public:    
    matrix( int nRows, int nCols )
        : m_nCols(nCols), m_rows(nRows), m_cells(nRows * nCols)
    {
        while (nRows--) m_rows[nRows] = &m_cells[nRows * nCols];
    }

    matrix( const matrix<T>& other )
        : m_nCols(other.m_nCols), m_rows(other.m_rows.size()), m_cells(other.m_cells)
    {
        int nRows = other.m_rows.size();
        while (nRows--) m_rows[nRows] = &m_cells[nRows * nCols];
    }

    void swap( matrix& other )
    {
        using std::swap;
        swap(m_nCols, other.m_nCols);
        swap(m_rows, other.m_rows);
        swap(m_cells, other.m_cells);
    }

    matrix& operator=( matrix other )
    {
        other.swap(*this);
        return *this;
    }

    const T& operator()( int row, int col ) const { return m_cells[makeIndex(row,col)]; }
    T& operator()( int row, int col ) { return m_cells[makeIndex(row,col)]; }
};

Деструктор std::vector позаботится об освобождениипамяти, и поскольку эти два выделения являются отдельными объектами, если m_cells не сможет выделить свою память, деструктор для m_rows запустится, и ничего не просочится.

Конечно, std::vector немногоИзлишне здесь, массив RAII фиксированного размера будет достаточно.Но std::auto_ptr нельзя использовать с массивами.Я думаю, что C ++ 0x должен добавить стандартный массив массивов RAII фиксированного размера.

РЕДАКТИРОВАТЬ: По запросу, 3-D версия:

#include <vector>
template<typename T>
class cube
{
    int m_nCols, m_nRows;
    std::vector<T> m_cells;
    size_t makeIndex( int row, int col, int channel ) const { return (channel*m_nRows + row)*m_nCols + col; }

public:    
    cube( int nRows, int nCols, int nChannels )
        : m_nCols(nCols), m_nRows(nRows), m_cells(nRows * nCols * nChannels)
    {
    }

    cube( const cube<T>& other )
        : m_nCols(other.m_nCols), m_nRows(other.m_nRows), m_cells(other.m_cells)
    {
    }

    void swap( cube& other )
    {
        using std::swap;
        swap(m_nCols, other.m_nCols);
        swap(m_nRows, other.m_nRows);
        swap(m_cells, other.m_cells);
    }

    cube& operator=( cube other )
    {
        other.swap(*this);
        return *this;
    }

    const T& operator()( int row, int col, int channel ) const { return m_cells[makeIndex(row,col,channel)]; }
    T& operator()( int row, int col, int channel ) { return m_cells[makeIndex(row,col,channel)]; }

    class channel_iterator
    {
        cube& const cube;
        int const row, col;
        int channel;
        friend class cube;
        channel_iterator( cube& all, int r, int c, int n ) : cube(all), row(r), col(c), channel(n) {}
    public:
        T& operator*() const { return cube(row, col, channel); }
        channel_iterator& operator++() { ++channel; return *this; }
        channel_iterator operator++(int) { return channel_iterator(cube, row, col, channel++); }
        bool operator!=(const channel_iterator& other) const { assert(&cube == &other.cube); return (row == other.row && col == other.col && channel == other.channel); }
    };

    int channel_count() const { return m_cells.size() / m_nRows / m_nChannels; }
    pair<channel_iterator, channel_iterator> range(int row, int col) { return make_pair(channel_iterator(*this, row, col, 0), channel_iterator(*this, row, col, channel_count())); }
};
1 голос
/ 17 февраля 2011

Лучшим шаблоном для реализации присваивания является идиома копирования и обмена.

X& X::operator= (const X& rhv)
{
    X copy(rhv); //reuse copy constructor
    this->swap(copy); //reuse swap method - also useful for the user of the class!
    //previously held resources automatically released by the destructor of copy
} 

Выполняет необходимые шаги в порядке, в котором значение левой руки остается неизменным, если во время копирования возникает исключение: hasn 'высвободить ресурсы до того, как новые ресурсы будут успешно получены.

И преимущество перед вашими методами в том, что метод swap полезен не только для реализации класса, но и для пользователя класса.В случае класса CMatrix реализация будет выглядеть следующим образом:

void CMatrix<T>::swap(CMatrix<T>& other)
{
    std::swap(m_pData, other.m_pData);
    std::swap(m_nRows, other.m_nRows);
    std::swap(m_nCols, other.m_nCols);
    std::swap(m_nChannels, other.m_nChannels);
}

Также, если это целесообразно, лучше повторно использовать существующие классы RAII вместо ручного управления памятью в каждом классе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...