0

假设我们有以下内容:

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;

我正在阅读的教科书(Absolute C++)说,在delete [] a;“指针 sa 未定义。赋值运算符损坏了对象 s 并且该程序的运行可能被破坏了”之后。

为什么运营商破坏了s?如果我们在删除 sa 后立即重新初始化它,为什么这会导致程序中出现这样的问题,我们必须将函数重新定义为:

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

5 回答 5

5

如果您将一个对象分配给它自己art.a指向同一个字符串,那么当您这样做时,delete [] a您将同时删除什么art.a指向;然后你确实重新分配它,但是你要在循环中复制(在其自身上)的数据已经在delete.

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

顺便说一句,即使使用自赋值的“安全网”检查赋值运算符也不完全正常(例如,它不是异常安全的);定义“三巨头”(复制构造函数、赋值运算符、析构函数)的“安全”方法是使用“复制和交换习语”。

于 2012-11-27T01:26:30.900 回答
3

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

于 2012-11-27T01:26:50.840 回答
2

考虑rtSide.a当你在里面时的价值是什么 broken operator=

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.

如果您确实想要这样做,则必须在删除之前制作 a 的副本:

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;

于 2012-11-27T01:26:40.347 回答
1

这是因为您首先删除了指针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.

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

于 2012-11-27T01:26:57.087 回答
0
delete [] a;
a = new char[capacity];

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

这就是为什么。可以这样想:

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

请记住,this两者rtSide都将您引向同一个对象。当你使用this对象修改对象时,rtSide引用的对象就被修改了。

于 2012-11-27T01:28:51.370 回答