在C++中复制构造函数和=运算符重载:可能是一个常见的函数吗?

MPe*_*ier 81 c++ c++-faq variable-assignment copy-constructor

自复制构造函数

MyClass(const MyClass&);
Run Code Online (Sandbox Code Playgroud)

和=运算符重载

MyClass& operator = (const MyClass&);
Run Code Online (Sandbox Code Playgroud)

有几乎相同的代码,相同的参数,只有不同的返回,是否有可能有一个共同的功能,他们都使用?

CB *_*ley 114

是.有两种常见的选择.一个 - 通常不鼓励 - 是operator=显式调用复制构造函数:

MyClass(const MyClass& other)
{
    operator=(other);
}
Run Code Online (Sandbox Code Playgroud)

然而,operator=在处理旧状态和自我分配引起的问题时,提供商品是一项挑战.此外,即使要将其分配给,所有成员和基础也会首先进行默认初始化other.这甚至可能对所有成员和基地都无效,即使它有效,它在语义上也是多余的,并且可能实际上很昂贵.

越来越流行的解决方案是operator=使用复制构造函数和交换方法来实现.

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

甚至:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

一个swap函数通常是简单的写,因为它只是交换了内部的所有权,并没有清理现有状态或分配新的资源.

复制和交换习惯用法的优点是它可以自动进行自我分配安全 - 并且 - 假设交换操作是无抛出的 - 也非常安全.

为了强烈异常安全,"手写"赋值运算符通常必须在取消分配受让人的旧资源之前分配新资源的副本,以便在分配新资源时发生异常,旧状态仍然可以返回到.所有这些都是免费提供的复制和交换,但通常更复杂,因此容易出错,从头开始.

要注意的一件事是确保交换方法是真正的交换,而不是std::swap使用复制构造函数和赋值运算符本身的默认值.

通常使用成员swap方式.std::swap所有基本类型和指针类型都可以保证"无法投入".大多数智能指针也可以与无投掷保证交换.

  • 也许是"我不推荐",添加"并且没有任何C++专家".有人可能会出现并且没有意识到你不只是表达了个人的少数民族偏好,而是那些真正想过它的人的既定共识意见.而且,好吧,也许我错了,一些C++专家确实推荐它,但就我个人而言,我仍然会为有人提出这个建议的参考资料. (14认同)
  • 很公平,我无论如何都支持你:-).我认为,如果某些东西被广泛认为是最佳实践,那么最好这样说(如果有人说它毕竟不是最好的话,再看看它).同样,如果有人问"是否有可能在C++中使用互斥体",我不会说"一个相当常见的选择是完全忽略RAII,并编写在生产中死锁的非异常安全代码,但它越来越受欢迎体面的,有效的代码";-) (4认同)
  • +1.而且我认为总是需要进行分析.我认为在某些情况下(对于轻量级类),复制ctor和赋值运算符都使用`assign`成员函数是合理的.在其他情况下(资源密集型/使用案例,句柄/正文),复制/交换当然是必须的. (4认同)
  • 实际上,它们不是常见的操作.复制ctor首次初始化对象的成员时,赋值运算符会覆盖现有值.考虑到这一点,复制ctor中的所谓`operator =`实际上非常糟糕,因为它首先将所有值初始化为某个默认值,只是为了在之后用其他对象的值覆盖它们. (3认同)
  • @litb:我对此感到惊讶所以我查看了Exception C++中的第41项(这已经变成了),这个特别的建议已经消失了,他建议使用copy-and-swap代替它.相反,他悄悄地放弃了"问题#4:分配效率低下". (2认同)

sbi*_*sbi 13

复制构造函数执行以前是原始内存的对象的首次初始化.赋值运算符OTOH使用新值覆盖现有值.通常,这涉及到解雇旧资源(例如,内存)和分配新资源.

如果两者之间存在相似性,则赋值运算符执行销毁和复制构造.一些开发人员过去常常通过就地破坏实现赋值,然后是贴片复制构造.但是,这是一个非常糟糕的主意.(如果这是在派生类的赋值期间调用的基类的赋值运算符,该怎么办?)

swap正如查尔斯建议的那样,现在通常被认为是典型的习语:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

这使用了复制构造(注释other被复制)和销毁(它在函数末尾被破坏) - 它也以正确的顺序使用它们:构造(可能在失败之前失败)(绝不能失败).