由于复制构造函数
MyClass(const MyClass&);
和=运算符重载
MyClass& operator = (const MyClass&);
具有几乎相同的代码,相同的参数,并且仅在返回值上有所不同,是否可以为它们两者使用共同的功能?
由于复制构造函数
MyClass(const MyClass&);
和=运算符重载
MyClass& operator = (const MyClass&);
具有几乎相同的代码,相同的参数,并且仅在返回值上有所不同,是否可以为它们两者使用共同的功能?
Answers:
是。有两种常见的选择。一种方法(通常不鼓励使用)是operator=
从复制构造函数中显式调用:
MyClass(const MyClass& other)
{
operator=(other);
}
但是,operator=
在处理旧状态和自我分配引起的问题时,提供一种物品是一个挑战。同样,所有成员和基础都将首先默认初始化,即使要将它们分配给from other
。这甚至可能对所有成员和基地都无效,即使它是有效的,它在语义上也是多余的,并且实际上可能很昂贵。
越来越流行的解决方案是operator=
使用复制构造函数和交换方法来实现。
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
甚至:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
一个swap
函数通常是简单的写,因为它只是交换了内部的所有权,并没有清理现有状态或分配新的资源。
复制和交换惯用语的优点在于,它是自动自我分配安全的,并且-只要交换操作为非抛出条件-也是异常例外安全的。
为了严格保护例外,“手写”书面分配操作员通常必须先分配新资源的副本,然后再取消分配受让人的旧资源,这样,如果在分配新资源时发生异常,仍然可以将旧状态返回到。所有这些都是通过复制和交换免费提供的,但通常更复杂,因此从头开始就容易出错。
要注意的一件事是确保swap方法是一个真正的交换,而不是std::swap
使用复制构造函数和赋值运算符本身的默认交换。
通常使用成员swap
方式。std::swap
有效,并且所有基本类型和指针类型均保证“不抛出”。大多数智能指针也可以与无掷保证互换。
operator=
复制ctor的所有功能实际上是非常糟糕的,因为它首先将所有值初始化为某个默认值,然后才用另一个对象的值覆盖它们。
assign
在某些情况下(对于轻量级类)让复制ctor和赋值运算符同时使用成员函数是合理的。在其他情况下(资源密集型/使用情况,句柄/正文),复制/交换是当然的方法。
复制构造函数对曾经是原始内存的对象执行首次初始化。赋值运算符OTOH用新值覆盖现有值。通常涉及解散旧资源(例如,内存)并分配新资源。
如果两者之间有相似之处,那就是赋值运算符执行销毁和复制构造。一些开发人员过去实际上是通过就地销毁,然后是布局复制构造来实现分配的。但是,这是一个非常糟糕的主意。(如果这是派生类的分配期间调用的基类的赋值运算符,该怎么办?)
如今,通常被视为规范用语的是swap
Charles所建议的:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
这使用了复制构造(注意other
已复制)和销毁(在函数末尾对其进行了销毁)-并且它也以正确的顺序使用它们:销毁(必须失败)之前的构造(可能会失败)。
有些事情困扰着我:
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
首先,当我的思想是“复制”时读“交换”一词会激怒我的常识。另外,我对这个幻想的目的提出了质疑。是的,在构造新的(复制的)资源时,任何异常都应该发生在交换之前,这似乎是一种确保所有新数据都被填充后才能上线的安全方法。
没关系。那么,交换之后发生的异常又如何呢?(当临时对象超出范围时旧资源被破坏时)从分配用户的角度来看,该操作失败了,但没有失败。它具有巨大的副作用:复制确实发生了。只是某些资源清理失败。即使从外部看来操作失败,目标对象的状态也已更改。
因此,我建议不要“交换”来进行更自然的“转移”:
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
transfer(tmp);
return *this;
}
仍然有临时对象的构造,但是下一个立即的操作是释放源的所有当前资源,然后再将源的资源移动(并为NULL,这样它们就不会被双重释放)。
我提出了{构造,破坏,移动}而不是{构造,移动,破坏}。此举是最危险的举动,是在解决所有其他问题后采取的最后一步。
是的,无论哪种方案,销毁失败都是一个问题。数据已损坏(在您不认为是复制时复制)或丢失(在您不认为是被释放时释放)。丢失总比损坏好。没有数据比坏数据更好。
转移而不是交换。无论如何,这是我的建议。
First, reading the word "swap" when my mind is thinking "copy" irritates
->作为图书馆作家,您通常会知道一些常见的做法(copy + swap),而症结所在my mind
。您的想法实际上隐藏在公共界面的后面。这就是可重用代码的全部意义。