赋值重载:为什么使用相同的对象会导致程序中出现问题

Assignment overloading: why does using the same object cause problems in the program?

本文关键字:程序 问题 对象 为什么 重载 赋值      更新时间:2023-10-16

>假设我们有以下内容:

class StringClass
{
public:
    ...
    void someProcessing( );
    ...
    StringClass& operator=(const StringClass& rtSide);
    ...
private:
    char *a;//Dynamic array for characters in the string
    int capacity;//size of dynamic array a
    int length;//Number of characters in a
};
StringClass& StringClass::operator=(const StringClass& rtSide)
{
    capacity = rtSide.capacity;
    length = rtSide.length;
    delete [] a;
    a = new char[capacity];
    for (int i = 0; i < length; i++)
       a[i] = rtSide.a[i];
    return *this;
}

我的问题是:为什么当我们尝试将对象分配给自身时,这种重载赋值运算符的实现会导致问题,例如:

StringClass s;
s = s;

我正在阅读的教科书(绝对C++)说,在delete [] a;"指针s.a是未定义的。 赋值运算符损坏了对象,程序的运行可能被破坏了。

为什么运算符损坏了 s? 如果我们在删除 s.a 后立即重新启动它,为什么这会导致程序中出现这样的问题,以至于我们必须将函数重新定义为:

StringClass& StringClass::operator=(const StringClass& rtSide)
{
    if (this == &rtSide)
    //if the right side is the same as the left side
    {
        return *this;
    }
    else
    {
        capacity = rtSide.capacity;
        length = rtSide.length;
        delete [] a;
        a = new char[capacity];
        for (int i = 0; i < length; i++)
            a[i] = rtSide.a[i];
        return *this;
    }
}

如果你将一个对象分配给它,art.a都指向同一个字符串,所以当你这样做delete [] a时,你正在删除art.a指向的内容; 然后你确实重新分配它,但你要在循环中复制的数据(在自己身上)在delete中丢失了。

现在在循环中,您只需复制new自身返回的内存中的任何垃圾。

顺便说一下,即使使用自赋值的"安全网"检查赋值运算符

也不是完全可以的(例如,它不是异常安全的);定义"三巨头"(复制构造函数,赋值运算符,析构函数)的"安全"方法是使用"复制和交换习惯用法"。

如果你是自分配的,你通过LHS参数释放(delete)字符串,然后再通过RHS参数将其复制到新分配的空间。这不是幸福的秘诀;这是未定义的行为,任何事情都可能发生。 崩溃是合理的;如果你真的不走运,它看起来可能会起作用。

当你在破碎的operator=内时,考虑一下rtSide.a的值是多少。

它和this->a一样,你刚刚破坏的值。 访问非拥有内存是未定义的行为,因此访问 this->a 是未定义的行为(因为您刚刚释放了它)。

delete [] a;
a = new char[capacity];
for (int i = 0; i < length; i++)
   a[i] = rtSide.a[i]; //Invalid when this->a == rtSide.a 
   //because rtSide.a is no longer owned by your program.

如果您确实想这样做,则必须在删除之前复制 :

char* ca;
if (this == &rtSide) {
    ca = copy of rtSide.a or this->a;
} else {
    ca = rtSide.a;
}
//Do your assigning and whatnot
if (this == &rtSide) {
    delete[] ca;
}

显然,不执行任何操作比临时复制所有实例自己的成员要高效得多。 这与做int x = 5; int y = x; x = y;的概念相同

这是因为您首先删除了指针delete [] a;
然后稍后尝试从已删除的位置复制:

for (int i = 0; i < length; i++)
       a[i] = rtSide.a[i]; //rtSide has already been deleted as 'this' and '&rtSide' are same.

请记住,它与您尝试从中复制的位置相同,您已经删除了该位置。因此,错误!
您发布的更高代码通过将自分配作为单独的案例进行检查来解决此问题。

delete [] a;
a = new char[capacity];
for (int i = 0; i < length; i++)
   a[i] = rtSide.a[i];

这就是原因。可以这样想:

删除指向的任何内容,然后分配新的内存块。新的内存块包含垃圾,这些垃圾将成为您的新数据。不要被只将垃圾复制到自身上的循环a[i] = rtSide.a[i];混淆。

请记住,thisrtSide都会引导您找到同一个对象。使用 this 修改对象时,将修改rtSide引用的对象。