用于类构造函数、析构函数和运算符重载的实用函数

发布于 2024-10-17 18:05:21 字数 4104 浏览 2 评论 0原文

不久前,我在一个网站上发现了一些实用函数的代码示例,这些函数在创建析构对象时使用,甚至在重载某些运算符<时使用。 /代码>。 更准确地说,主要使用以下成员函数:init、copy、set和destroy。

  • init 成员函数用于初始化所有私有成员。它主要在构造函数内部调用,例如默认参数构造函数
  • copy 成员函数用于对作为 const 引用 传递的对象进行深层复制。它在引用构造函数operator =的重载内部调用。
  • set 成员函数主要为需要它的私有成员分配内存。
  • 最后,destroy 成员函数用于释放分配的内存。例如,它在析构函数内部被调用。

我想听听您的意见并知道这是否是一个好的编程实践?有哪些优点或缺点?欢迎任何意见和建议! 下面,我将说明如何为 CMatrix 类定义这些成员函数。

矩阵.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;
};

矩阵.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;
    } 
}

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 init member function is used to initialize all the private members. It's mostly called inside the constructors, e.g. the default or parameter constructor.
  • The copy member function is used to do a deep copy of an object passed as a const reference. It is called inside the reference constructor and the overload of the operator =.
  • The set member function which mainly allocates memory for the private members that require it.
  • Finally, the destroy member function is used for releasing the allocated memory. It's called, for example, inside of the destructor.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

千里故人稀 2024-10-24 18:05:21

不,不建议这样做。您建议的方式不是异常安全的,并且与 const 或需要非默认构造的子对象不兼容。

请改用ctor-initializer-list。代码重用可以通过在构造函数初始化列表中调用的静态辅助函数或将逻辑移至子对象构造函数中来实现。

对于内存分配,每个资源使用一个子对象。内存管理逻辑最终出现在子对象构造函数和析构函数中。在许多情况下,您可以使用库中现有的 RAII 类,例如 std::vector,而无需自己编写任何内存管理代码。

大多数运算符可以使用 copy-and-swap 惯用法重用构造函数中的逻辑

编辑:异常安全构造可能看起来像这样:

#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 数组类。

编辑:根据请求,3D 版本:

#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())); }
};

No, this is not recommended. The way you propose is not exception safe and is not compatible with const or 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:

#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)]; }
};

The std::vector destructor will take care of freeing the memory, and since the two allocations are separate objects, if m_cells fails to allocate its memory, the destructor for m_rows will run and nothing will leak.

Of course, std::vector is a little bit overkill here, a fixed-size array RAII class would be sufficient. But std::auto_ptr can'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:

#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())); }
};
安静被遗忘 2024-10-24 18:05:21

实现分配的更好模式是复制和交换习惯用法。

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
} 

如果复制过程中发生异常,这将按照保持左侧值不变的顺序执行必要的步骤:在成功获取新资源之前不会释放资源。

与您所拥有的方法相比,交换方法的优点是,交换方法不仅对于内部实现类有用,而且对于类的用户也有用。对于 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 类,而不是在每个类中手动管理内存。

A better pattern for implementing assignment is the copy and swap idiom.

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
} 

This performs the necessary steps in an order that keeps left-hand value unchanged if an exception occurs during the copying: doesn't release resources before new resources have been successfully obtained.

And the advantage over the methods you have is that the swap method is useful not only internally for implementing the class, but also for the user of the class. In case of the CMatrix class the implementation would be:

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

Also, if suitable, it is a better idea to reuse existing RAII classes instead of managing memory manually in each and every class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文