在赋值运算符中使用复制构造函数

时间:2014-09-14 05:14:48

标签: c++ operator-overloading copy-constructor

在赋值运算符中使用复制构造函数是否违反样式指南?即:

const Obj & Obj::operator=(const Obj & source)
{
    if (this == &source)
    {
        return *this;
    }

    // deep copy using copy-constructor
    Obj * copy = new Obj(source);

    // deallocate memory
    this->~Obj();

    // modify object
    *this = *copy;

    return *copy;
}

假设copy-constructor在对象上执行深层复制。


编辑:

我的代码非常错误,正如评论者指出的那样。

至于整体概念问题:正如WhozCraig建议的那样,复制/交换习惯似乎是要走的路:What is the copy-and-swap idiom?

3 个答案:

答案 0 :(得分:3)

以下是您的示例中的复制/交换习惯用语:

#include <algorithm>

class Obj
{
    int *p;
    void swap(Obj& left, Obj& right);

public:
    Obj(int x = 0) : p(new int(x)) {}
    Obj(const Obj& s);
    Obj& operator = (const Obj& s);
    ~Obj() { delete p; }
};

Obj::Obj(const Obj& source) : p(new int(*source.p))
{}

void Obj::swap(Obj& left, Obj& right)
{
    std::swap(left.p, right.p);
}

Obj & Obj::operator=(const Obj & source)
{
    Obj temp(source);
    swap(*this, temp);
    return *this;
}

int main()
{
    Obj o1(5);
    Obj o2(o1);
    Obj o3(10);
    o1 = o3;
}

要了解它是如何工作的,我有目的地创建了一个成员,它是一个指向动态分配内存的指针(如果没有用户定义的复制构造函数和赋值运算符,这将会出现问题)。

如果您专注于赋值运算符,它会调用Obj复制构造函数来构造临时对象。然后调用Obj特定的swap来交换各个成员。现在,在temp被调用之后,魔法就在swap对象中。

当调用temp的析构函数时,它将调用delete指针this曾经拥有的值,但是与temp指针交换出来。因此,当temp超出范围时,它会清除“旧”指针分配的内存。

另请注意,在赋值期间,如果new在创建临时对象期间抛出异常,则赋值将在this的任何成员发生更改之前抛出异常。这可以防止对象因无意中更改而导致成员损坏。

现在,之前的答案是使用常用的“共享代码”方法来复制分配。以下是此方法的完整示例,并解释了它存在问题的原因:

class Obj
{
    int *p;
    void CopyMe(const Obj& source);

public:
    Obj(int x = 0) : p(new int(x)) {}
    Obj(const Obj& s);
    Obj& operator = (const Obj& s);
    ~Obj() { delete p; }
};

void Obj::CopyMe(const Obj& source)
{
    delete p;
    p = new int(*source.p);
}

Obj::Obj(const Obj& source) : p(0)
{
   CopyMe(source);
}

Obj & Obj::operator=(const Obj & source)
{
    if ( this != &source )
        CopyMe(source);
    return *this;
} 

所以你会说“这有什么问题?”那么,错误的是CopyMe首先要做的就是调用delete p;。然后你要问的下一个问题是“那又怎么样?这不是我们应该做的,删除旧记忆吗?”

这个问题是后续调用new可能会失败。所以我们所做的就是在我们知道新数据可用之前销毁我们的数据。如果new现在抛出异常,我们就搞砸了我们的对象。

是的,您可以通过创建临时指针,分配来轻松修复它,最后将临时指针分配给p。但很多时候这可以被遗忘,并且上面的代码永远存在于代码库中,即使它有潜在的破坏错误。

为了说明,以下是CopyMe的修正:

void Obj::CopyMe(const Obj& source)  
{
    int *pTemp = new int(*source.p);
    delete p;
    p = pTemp;
}

但是,你会看到大量的代码使用了具有这个潜在错误的“共享代码”方法,并且没有提到关于异常问题的一个词。

答案 1 :(得分:1)

您要做的事情不起作用。您似乎认为赋值运算符返回的对象变为新的“this”,但事实并非如此。 你没有修改过对象,只是将其销毁了。

Obj a;
Obj b;
a = b; // a has been destroyed
       // and you've leaked a new Obj.
// At the end of the scope, it will try to destroy `a` again, which
// is undefined behavior.

可以使用展示位置new来实现赋值运算符,但它是一个易碎的解决方案,通常不建议使用:

const Obj & Obj::operator=(const Obj & source)
{
    if (this == &source)
    {
        return *this;
    }

    this->~Obj();

    new (this) Obj(source);

    return *this;
}

如果在构造期间可能出现异常,则会导致问题,并且可能导致派生类出现问题。

答案 2 :(得分:-2)

在复制构造函数和assigmnet运算符之间共享代码是完全有意义的,因为它们通常执行相同的操作(将作为参数属性传递的对象复制到此)。

Personnaly,我经常通过巧妙地编写赋值运算符然后从复制构造函数中调用它来完成它:

Obj::Obj(const Obj & source)
{
    Obj::operator=( source );
}

const Obj& Obj::operator=(const Obj& source)
{
    if (this != &source)
    {
        // copy source attribtes to this.
    }    
    return *this;
}

如果正确编写operator=,它就有效。 如评论所述,可能会建议您使用交换功能Copy constructor and = operator overload in C++: is a common function possible?

无论如何,你在两个函数之间共享代码的想法很好,但是你实现它的方式却没有。工作肯定..有很多问题,并没有做你的意思。它以递归方式调用operator =。此外,你永远不应该像你一样(this->~Obj();)明确地调用析构函数。