在赋值运算符中使用复制构造函数
Using copy constructor in assignment operator
在赋值运算符中使用复制构造函数是否违反样式准则?即:
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所建议的那样,复制/交换习惯用法似乎是可行的:什么是复制和交换习惯用法?
以下是示例中的复制/交换习惯用法:
#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
来交换各个成员。现在,在调用swap
之后,魔法就在temp
对象中。
当调用temp
的析构函数时,它将对this
曾经具有的指针值调用delete
,但已被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;
}
但是,您将再次看到大量使用"共享代码"方法的代码存在此潜在错误,而对异常问题只字不提。
您试图做的事情不起作用。您似乎认为赋值运算符返回的对象变成了新的"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;
}
如果在构造过程中可能出现异常,并且可能导致派生类出现问题,则会导致问题。
在复制构造函数和assignnet运算符之间共享代码是完全有意义的,因为它们经常执行相同的操作(将作为参数属性传递的对象复制到此)。
就我个人而言,我经常通过巧妙地对赋值运算符进行编码,然后从复制构造函数调用它:
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=
,它就会起作用如前所述,建议使用一个交换函数:复制C++中的构造函数和=运算符重载:是否可以使用通用函数?
无论如何,您在两个函数之间共享代码的想法是好的,但您实现它的方式并不好。工作是肯定的。。它有很多问题,并没有达到你想要的目的。它递归地调用运算符=。此外,您永远不应该像以前那样显式调用析构函数(this->~Obj();
)。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 当从函数参数中的临时值调用复制构造函数时
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 为什么类中的ostringstream类型的成员会导致";调用隐含删除复制构造函数";错误
- 复制构造函数、赋值运算符C++
- std::ofstream 作为类成员删除复制构造函数?
- 复制构造函数C++无法正确复制指针
- 关于复制构造函数的一个棘手问题
- 为什么调用复制构造函数而不是移动构造函数?
- 填充上编译器生成的复制构造函数之间的不一致
- C++ 对象指针数组的复制构造函数
- C++ 基本 CTOR 说明 - 为什么不调用赋值/复制构造函数
- 防止在复制构造函数中隐式调用基构造函数
- 为用户定义的类正确调用复制构造函数/赋值运算符
- 具有已删除移动和复制构造函数的类的就地构造
- 复制构造函数隐式转换问题
- 复制构造函数中的递归调用