C++:使用重载复合赋值运算符时出现运行时错误

4
我有一个程序,使用一个类来动态分配数组。我重载了运算符来对该类的对象进行操作。
当我测试这个程序时,重载的 += 运算符可以正常工作,但 -= 却无法工作。当尝试运行重载的 -= 时,程序崩溃并出现以下运行时错误:
malloc:*错误对象为0x7fd388500000:释放的指针未被分配*请在malloc_error_break中设置断点以进行调试。
在私有成员变量中,我像这样声明数组:
double* array_d;

然后我在重载的构造函数中动态分配数组:

Students::Students(int classLists)
{
    classL = classLists;
    array_d = new double[classL];
}

我有以下两个重载构造函数,作为Students类的友元函数定义:

friend Student operator+= (const Student&, const Student&);
friend Student operator-= (const Student&, const Student&);

这些定义如下:
Student operator+= (const Student& stu1, const Student& stu2)
{
    if (stu1.getClassL() >= stu2.getClassL())
    {
        for (int count = 0; count < stu.getClassL(); count++)
            stu1.array_d[count] += stu2.array_d[count];

        return (stu1);
    }
    else if (stu1.getClassL() < stu2.getClassL())
    {
        for (int count = 0; count < stu1.getClassL(); count++)
                stu1.array_d[count] += stu2.array_d[count];

        return (stu1);
    }
}

Student operator-= (const Student& stu1, const Student& stu2)
{
    if (stu1.getClassL() >= stu2.getClassL())
    {
        for (int count = 0; count < stu2.getClassL(); count++)
            stu1.array_d[count] -= stu2.array_d[count];

        return (stu1);
    }
    else if (stu1.getClassL() < stu2.getClassL())
    {
        for (int count = 0; count < stu1.getClassL(); count++)
                stu1.array_d[count] -= stu2.array_d[count];

        return (stu1);
    }
}

基本上这里发生的事情是,我正在比较两个根据 classL 大小不同的数组对象。getClassL() 函数仅为:int Student::getClassL() const {return classLists;}。
如果你想知道,我已经按照以下方式重载了三个重要函数:
1. 析构函数:Student::~Student() {delete [] array_d;}
2. 拷贝构造函数:
Student::Student(const Student &student)
{
    classLists = student.classLists;
    array_d = student.array_d;
}

3. 赋值运算符:

Student &Student::operator=(const Student &student)
{
    classLists = student.classLists;
    array_d = student.array_d;
}

+= 可以使用,但 -= 不起作用,这很奇怪,因为它们实际上是相同的操作。我怀疑问题出在动态内存分配上,但我不确定,正在寻求专家的建议。


3
operator +=-= 应该返回当前对象的引用,而不是一个新的对象。因此,你的代码针对 +=-= 需要两个 Student 对象没有太多意义。这些函数应该通过引用接收单个 Student 对象并将其添加到 this,然后返回 *this。如果它们是 +- 运算符,那么带两个参数的函数会更有意义一些。 - PaulMcKenzie
2
你的复制构造函数不正确。你需要分配正确大小的数组并复制元素。你的赋值运算符也不正确。请参考这个链接:https://dev59.com/eG855IYBdhLWcg3wvXDd。 - NathanOliver
由于您在复制构造函数中复制了数组的地址,因此存在以下问题:假设A、B是学生,您将B复制到A中,B在某个时刻被销毁,并且其数组在析构函数中被删除,但A仍然持有指向该内存位置的指针。可以通过遵循@NathanOliver的建议来解决这个问题。 - bku_drytt
1
NathanOliver已经给出了答案。此外,如果您使用了std::vector,您就不会有这个问题。vector会为您处理它。您还应该考虑使用std::accumulate,或者考虑切换到基于范围的for循环,假设您有一个现代编译器。 - JVene
@PaulMcKenzie 我尝试在for循环中执行student.array_d[count] = this;,但它一直告诉我Student和double不兼容。我也不能简单地执行student = this。我正在尝试从一个点添加/减去数组元素到+=或-=右侧的任何内容。 - jshapy8
1
@revolution9540,我从未说过要将一个“Student”复制到您的数组元素中。请参见我的答案,了解有关您的“+=”和“-=”的信息。 - PaulMcKenzie
2个回答

3
给你的建议是使用std::vector,这样可以避免实现赋值运算符、复制构造函数和析构函数。
但是,即便是复制构造函数,代码中也存在一些问题。
首先,复制构造函数应该分配一个全新的数组,并将传入值中的数组的值复制到新数组中。这是一个简化版的Student类,只有两个成员——一个double*和一个表示数组元素数量的整数。
class Student
{
   int num;
   double *array_d;

   public:
      Student(const Student &student);
      Student& operator=(const Student &student);
      ~Student() { delete [] array_d; }
      Student() : array_d(0), num(0) {}
};

复制构造函数看起来会像这样:
Student::Student(const Student &student) : 
    array_d(new double[student.num]), num(student.num)
{
  for (int i = 0; i < num; ++i )
    array_d[i] = student.array_d[i];
}

一旦你拥有这个,使用复制/交换就可以轻松地进行赋值操作:

Student& Student::operator=(const Student &student)
{
    Student temp = student;
    std::swap(d_array, temp.d_array);
    std::swap(num, temp.num);
    return *this;
}

以上所有的工作都是进行传入对象的临时复制,将临时对象的内部与当前对象进行交换,然后临时对象就会带着旧的内部消失。所有这些操作都必须在学生(Student) 的复制构造函数和析构函数正常工作的情况下才能实现(现在应该已经正常了)。


接下来要考虑的是关于运算符+=-= 的整个想法。 大多数程序员期望使用方式如下:

Student a;
Student b;
// assume a and b are initialized and have data...
a += b;

如果你在不同的形式中使用+=-=,它会变得晦涩难懂并且很奇怪。因此,这些函数应该只接收一个参数而不是两个,并返回当前对象(即正在更改的当前对象)。
因此,这些函数不应该是友元函数,而应该是Student类的成员函数。
class Student
{
   int num;
   double *array_d;

   public:
      Student(const Student &student);
      Student& operator=(const Student &student);
      ~Student() { delete [] array_d; }
      Student() : array_d(0), num(0) {}
      Student& operator += (const Student& rhs);
      Student& operator -= (const Student& rhs);
 };

那么对于 +=,实现大致如下:
#include <algorithm>
//...
Student& operator+= (const Student& stu1)
{
    int num_to_add = std::min(num, stu1.num);
    for (int count = 0; count < num_to_add; ++count)
        array_d[count] += stu1.array_d[count];
    return *this;
}

同样,-=将如上所述。请注意使用std :: min来确定需要添加的数量,而不是使用带有if / else的原始代码。

作为一种提醒,复制并交换的方法虽然易懂,但速度相对较慢。通常情况下这并不重要。 - Mooing Duck

1
你的问题更多是关于为什么C++11/C++14比以前的版本更好,以及为什么应该将其用作这样的学习。首先,正如评论所建议的那样,运算符+=和-=应该返回一个引用,而不是一个副本。如果它是一个成员函数运算符,它将返回*this,但在你的情况下不是。只需将返回类型更改为Student &即可。同样,在注释中涵盖的崩溃原因是,当复制构造函数或赋值执行时,array_d的所有权被两个“Student”对象假定。当第二个对象被销毁时,它将尝试删除[] array_d,而这已经被第一个对象删除了。所示的赋值运算符没有返回任何内容。它应该返回*this。评论指出,您需要创建一个新数组,并将源中的每个元素复制到目标中,因此有两个单独的数组。然而,这是说明STL非常有价值的关键时刻。如果您使用std::vector<double>,则向量的赋值运算符将为您复制所有元素。
在这两个操作函数中(+=-=),"else if"子句没有价值。从逻辑上讲,如果stu1.getClassL()不是>= stu2.getClassL(),那么它只能是<,因此可以节省时间并删除else if子句以及包含的大括号。

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