为具有指针成员的类正确重载赋值运算符

4

如果我说错了,请纠正我:

我理解当类的成员是指针时,一个类对象的拷贝会导致指针表示相同的内存地址。这可能导致对一个类对象所做的更改影响所有该对象的副本。

解决方法可以是重载=运算符。在下面的示例中,尝试创建动态数组类,为什么对MyArray1进行更改会改变MyArray2

数组类:

#include <iostream>
#include <cstdlib>

class Array
{
public:
    Array(int N){               //constructor sets size of array
         size = N;
         arr = new int[N];
    }

    ~Array();

    int size;    //array elements
    int *arr;    //dynamic array pointer

    //fill array with random values between 1 and 100
    void fillArray() {
         for (size_t i = 0; i < size; i++)
              {arr[i] = std::rand()%100;}
    }

    //print out array to console
    void printArray() {
         for (size_t i = 0; i < size; i++)
             { std::cout << arr[i] << " ";}
             std::cout << std::endl;
    }

    //overload = operator
    Array &operator=(Array arr2) {
        std::swap(size, arr2.size);
        std::swap(arr, arr2.arr);
        return *this;
    }
};

Main.cpp:

 #include "Array.h"
 #include <iostream>

int main(){
    Array MyArray1(8), MyArray2(8);

    MyArray1.fillArray();
    MyArray2 = MyArray1;

    std::cout << "Print out arrays:" << std::endl;
    std::cout << "MyArray1: "; MyArray1.printArray();
    std::cout << "MyArray2: "; MyArray2.printArray();
    std::cout << std::endl;

    MyArray1.arr[5] = 1000;
    std::cout << "MyArray2: "; MyArray2.printArray();

    MyArray1.fillArray();
    std::cout << "MyArray2: "; MyArray2.printArray();    

    return 0;
}

示例输出:

Print out arrays:
MyArray1: 41 67 34 0 69 24 78 58
MyArray2: 41 67 34 0 69 24 78 58

MyArray2: 41 67 34 0 69 1000 78 58
MyArray2: 62 64 5 45 81 27 61 91

如上所示,对MyArray1所做的更改也会影响到MyArray2。我认为重载=是错误的,但正确的写法是什么呢?

解决方案:

感谢评论区的Chris Dodd,我意识到只需在我的类中实现以下复制构造函数即可:

Array(const Array &arr2){
        size = arr2.size;
        arr = new int[size];

        for (size_t i = 0; i < size; i++)
        {
            arr[i] = arr2.arr[i];
        }
}

你违反了三法则 - Chris Dodd
是的,我意识到了,这就是为什么我在你回答的同时删除了我的评论!谢谢,我会尝试一下 :) - remi
3个回答

10

你的代码存在多个问题。最重要的是,正如回答中指出的那样,除了编写自己的赋值运算符外,您还需要编写拷贝构造函数(并实现析构函数)。

其次,您的赋值运算符采用按值传递参数的方式,这会导致默认的拷贝构造函数在将MyArray1传递给赋值运算符之前创建其副本。这就是您问题的直接来源。

另一件事是,您的赋值运算符的行为类似于移动赋值而不是复制赋值,这意味着它将原始项的值替换为其当前(默认)值。

最后,您真的想实现析构函数,以便删除数组而不仅仅是泄漏它。

总之,您需要像这样实现(编辑:复制赋值检查受Timothy答案启发):

class Array
{
public:
    Array(int N)
    {
         size = N;
         arr = new int[N];
    }

    //destructor
    ~Array()
    {
        delete[] arr;
    }

    //copy constructor
    Array(const Array& arr2)
    {
        size = arr2.size;
        arr = new int[size];
        std::memcpy(arr, arr2.arr, size);
    }

    //overload = operator
    Array& operator=(const Array& arr2) 
    {
        if (this == &arr2)
            return *this; //self assignment
        if (arr != NULL)
            delete[] arr; //clean up already allocated memory

        size = arr2.size;
        arr = new int[size];
        std::memcpy(arr, arr2.arr, size);
        return *this;
    }

private:
    int size;    //array elements
    int *arr;    //dynamic array pointer
};

我注意到通过将我的赋值运算符改为使用引用,并重新运行代码后,MyArray1在执行MyArray2 = MyArray1之后“丢失”了所有信息。 - remi
1
第二件事是,你的赋值运算符采用按值传递参数而不是按引用传递。这是复制交换惯用法。 - juanchopanza
@juanchopanza 是的,你显然是正确的,我怀疑 OP 没有理解它的工作原理(因为没有实现复制构造函数)。 - slawekwin
请问您能否解释为什么编译器会报错:error: this ‘if’ clause does not guard... [-Werror=misleading-indentation] 31 | if (this == &arr2) - user786

1

所以,我看到三个问题。

  1. 你真的应该有一个拷贝构造函数。你永远不知道你的编译器会做出什么样的优化(将赋值更改为拷贝构造函数等)。

  2. 在赋值时应检查自我赋值。在执行赋值操作时,您希望摆脱旧数据并用新数据替换它。 如果进行自我赋值,您将遇到错误(即在重新分配给自己之前删除您的数据),而且这是您无需执行的额外工作。

  3. 在调用析构函数时,您没有处理动态分配的数据。这将导致内存泄漏,并对长时间运行的程序造成大问题。

代码更新:

~Array(){
  if (arr != NULL)
    delete arr;
}

Array(const Array& arr2){
  size = arr2.size;
  arr = new int[Size];
  for (int i = 0; i < n; ++i){ //Copy each array element into new array
    arr[i] = arr2.arr[i];
  }
}

Array& operator= (const Array& arr2){
  if (this == &arr2){ return *this; } //make sure you aren't self-assigning
  if (arr != NULL){ delete arr; } // get rid of the old data
  //same as copy constructor from here
  size = arr2.size;
  arr = new int[size];
  for (int i = 0; i < n; ++i){
    arr[i] = arr2.arr[i];
  }
  return *this;
}

关于第二点,OP正在使用复制并交换惯用法。这将对自我赋值具有鲁棒性。但由于复制构造函数的存在,它已经失效了。 - juanchopanza
好的。虽然对于大数组来说,这会导致很多额外的自我赋值工作。 - Timothy Murphy
2
你应该使用 delete [] 来删除数组。 - DigviJay Patil

0

如果你只想要一个等于号而不是数组,那么你可以避免所有这些问题。因为它还需要一个复制构造函数,所以不要使用MyArray1=MyArray2。你可以这样做。

Array& operator =(Array&arr)

通过引用传递解决问题,但如果你想要做其他事情。
Array MyArray1(MyArray2) ;  or     Array MyArray1=MyArray2

你需要一个拷贝构造函数

如果你想要自我赋值,你必须进行检查

if(this=&arr) {return *this;} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接