首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在赋值操作符中使用复制构造函数

在赋值操作符中使用复制构造函数
EN

Stack Overflow用户
提问于 2014-09-14 05:14:47
回答 3查看 2.5K关注 0票数 2

在赋值操作符中使用复制构造函数是否违反了样式准则?即:

代码语言:javascript
复制
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;
}

假设复制构造函数对对象执行深度复制。

编辑:

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

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-09-14 06:41:14

简单地说,下面是示例中的复制/交换成语:

代码语言:javascript
复制
#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-specific swap。现在,在调用temp对象之后,魔力就在swap对象中了。

当调用temp的析构函数时,它将对this过去拥有的指针值调用delete,但与temp指针交换掉。因此,当temp超出作用域时,它会清理由“旧”指针分配的内存。

另外,请注意,在赋值期间,如果new在创建临时对象期间抛出异常,则分配将在更改this的任何成员之前抛出异常。这样可以防止对象的成员由于不经意的更改而损坏。

现在,给出了一个以前的答案,使用常用的“共享代码”方法复制分配。这里是这个方法的完整示例,并解释了为什么它会出现问题:

代码语言:javascript
复制
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。但是很多时候,这是可以忘记的,而且像上面这样的代码永远保留在代码库中,即使它有潜在的损坏bug。

为了举例说明,下面是CopyMe的修复程序

代码语言:javascript
复制
void Obj::CopyMe(const Obj& source)  
{
    int *pTemp = new int(*source.p);
    delete p;
    p = pTemp;
}

但是,您将再次看到大量使用具有此潜在缺陷的“共享代码”方法的代码,而更不用说有关异常问题的一个字了。

票数 4
EN

Stack Overflow用户

发布于 2014-09-14 05:18:14

你想做的事行不通。您似乎认为赋值运算符返回的对象变成了新的"this",但事实并非如此。你没有修改对象,只是销毁了它。

代码语言:javascript
复制
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.

使用placement new实现赋值操作符是可能的,但它是一个脆弱的解决方案,通常不推荐:

代码语言:javascript
复制
const Obj & Obj::operator=(const Obj & source)
{
    if (this == &source)
    {
        return *this;
    }

    this->~Obj();

    new (this) Obj(source);

    return *this;
}

如果在构造过程中可能出现异常,则这会导致问题,并可能导致派生类出现问题。

票数 1
EN

Stack Overflow用户

发布于 2014-09-14 05:32:55

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

就个人而言,我通常通过巧妙地编写赋值操作符,然后从复制构造函数调用它:

代码语言:javascript
复制
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?都使用一个交换函数。

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

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25830312

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档