赋值重载:为什么使用同一个对象会导致程序出现问题?

时间:2012-11-27 01:23:05

标签: c++

假设我们有以下内容:

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;之后“指针sa然后是未定义的。赋值运算符已经破坏了对象s并且程序的运行可能已经毁了。”

为什么操作员损坏了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;
    }
}

5 个答案:

答案 0 :(得分:5)

如果您要为自己分配一个对象,art.a指向同一个字符串,那么当您执行delete [] a时,您同时删除了art.a {1}}指向;然后你重新分配它,但你要在循环中复制(自身)的数据已经在delete中丢失。

现在循环中你只需复制new返回的任何垃圾。

顺便说一句,即使使用自我分配检查的“安全网”,分配操作符也不完全正常(例如,它不是例外安全);定义“三巨头”(复制构造函数,赋值运算符,析构函数)的“安全”方法是使用“copy and swap idiom”。

答案 1 :(得分:3)

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

答案 2 :(得分:2)

当你在破碎的rtSide.a内时,请考虑operator=的价值。

它与this->a相同,你刚刚破坏了它们的价值。访问非拥有内存是未定义的行为,因此访问this-&gt; 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;

的概念相同

答案 3 :(得分: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.

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

答案 4 :(得分:0)

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引用的对象将被修改。