如何在具有动态大小数组的模板类中重载 operator=

Posted

技术标签:

【中文标题】如何在具有动态大小数组的模板类中重载 operator=【英文标题】:How to overload operator= in a templated class with a dynamically sized array 【发布时间】:2013-10-25 01:27:05 【问题描述】:

我是使用 C++ 创建自己的模板类的新手,在网上搜索了几个小时的答案并玩弄了函数及其参数后,我放弃了。我在使用以下类“=”运算符时遇到了运行时问题:

在matrix.h中:

template <class datatype> class Matrix
  datatype** element;
  unsigned int m,n;

  public:

  Matrix(unsigned int M, unsigned int N,datatype x)
    m=M;    // # of rows
    n=N;    // # of cols
    element=new datatype*[m];
    for(int i=0;i<m;i++) element[i]=new datatype[n];
    for(int i=0;i<m;i++)
      for(int j=0;j<n;j++)
        element[i][j]=x;
  

  void print()
    for(int i=0;i<m;i++)
      for(int j=0;j<n;j++) cout<<element[i][j]<<" ";
      cout<<"\n";
    
  

  Matrix operator=(Matrix A)
    for(int i=0;i<m;i++) delete[] element[i];
    delete[] element;
    m=A.m;
    n=A.n;
    element=new datatype*[m];
    for(int i=0;i<m;i++) element[i]=new datatype[n];
    for(int i=0;i<m;i++)
      for(int j=0;j<n;j++)
        element[i][j]=A.element[i][j];
    return *this;
  
;

当我去测试这个时,编译和链接运行顺利,没有错误,我得到一个完全有效的打印。但是当试图将一个矩阵分配给另一个矩阵的值时,程序崩溃并显示消息“matrix_test 已停止工作”。这是我在 matrix_test.cpp 中的测试代码:

Matrix<int> M(5u,3u,0);
Matrix<int> P(2u,7u,3);

int main()
    M.print();
    cout<<"\n";
    P.print();
    cout<<"\n";
    P=M;
    P.print();        

提前感谢您的帮助!

【问题讨论】:

您的赋值运算符签名应该类似于Matrix&amp; operator=(const Matrix&amp; A)。您还需要一个复制构造函数析构函数。见:What is The Rule of Three? @Blastfurnace:实际上,我认为签名绝对是正确的!但是,执行不! ;-) @DietmarKühl:我同意改用Matrix&amp; operator=(Matrix A) @Blastfurnace:好的,我没有注意到Matrix 也是按值返回的:这确实是个坏主意。按值传递参数是非常合理的,因为无论如何都需要副本(请参阅我的回答)。 @Dietmar Kühl:那么我将如何更改实现?我确实已经有一个析构函数和一个复制构造函数,我只是懒得在这里展示它们,认为它们不会影响我的问题的答案。但是实施有什么问题?再次感谢您的帮助! 【参考方案1】:

首先,复制分配的实现在一个相当基本的方面存在缺陷:当您 delete[] 表示然后分配新副本时,分配可能会抛出,在这种情况下您的原始矩阵是 delete[]d并且无法恢复。因此,分配不是异常安全的。

复制赋值运算符的最佳实现是利用复制构造和swap() 成员。当然,您的课程中缺少这两个成员,但让我们稍后再谈:

Matrix& Matrix::operator= (Matrix other) 
    other.swap(*this);
    return *this;

当按值传递参数时,它实际上是被复制的。要复制对象,您需要一个复制构造函数。通常,如果您需要复制分配,您通常还需要复制构造函数和析构函数(在某些情况下,您只需要复制分配即可使复制分配具有强异常安全性,但这是一个不同的讨论)。

复制构造函数的目的是复制另一个对象,例如,当对象通过值传递时:

Matrix::Matrix(Matrix const& other)
    : element(new datatype*[other.m])
    , m(other.m)
    , n(other.n)

    int count(0);
    try 
        for (; count != m; ++count) 
            this->element[count] = new datatype[m];
            std::copy(other.element[count], other.element[count] + m,
                      this->element[count]);
        
    
    catch (...) 
        while (count--) 
            delete[] this->element[count];
        
        delete[] this->element;
        throw;
    

我不确定从异常中恢复是否真的正确:我无法处理应对所有这些指针的复杂性!在我的代码中,我将确保所有资源立即构造一个专门用于自动释放它们的对象,但这需要更改对象的类型。给定类型的定义,还需要一个析构函数:

Matrix::~Matrix() 
    for (int count(this->m); count--; ) 
        delete[] this->element[count];
    
    delete[] this->element;

最后,对于较大的对象,swap() 成员通常很方便。 swap() 的目的只是交换两个对象的内容。实现它的方法是做一个会员std::swap():

void Matrix::swap(Matrix& other) 
    using std::swap;
    swap(this->element, other.element);
    swap(this->n, other.n);
    swap(this->m, other.m);

鉴于这个类的所有成员都是内置类型(尽管它们可能不应该是),using-dance 并不是真正需要的。但是,如果在 std::swap() 之外的其他命名空间中针对用户定义的类型存在专门的 swap() 重载,则上述方法可确保通过参数相关查找找到这些重载。

【讨论】:

谢谢!这绝对有帮助。我没有考虑在按值传递的对象上使用 std::swap 。一开始我实际上是在使用异常处理,但由于我声明(非常)小数组来测试它,我只是想确保基本操作首先工作。

以上是关于如何在具有动态大小数组的模板类中重载 operator=的主要内容,如果未能解决你的问题,请参考以下文章

模板化动态数组中的运算符重载 []

如何管理具有递归函数调用的模板类中的数组(以数组的长度作为参数)?

如何制作具有动态大小的数组?动态数组的一般用法(也可能是指针)? [关闭]

模板类和插入提取重载

尝试在模板类中重载 / 运算符的 C++ 错误

如何复制动态分配的对象(具有一个类的 const 成员)