如何实现拷贝交换习惯用法的复制构造函数



我试图实现一个类的复制构造函数和赋值操作符。我对拷贝交换习语有点困惑了。特别是当涉及到复制构造函数时。拷贝交换习惯用法与复制构造函数有任何关系吗?如何避免代码重复?

这是我的类

头:

Class Actor
{
public:
    Foo* foo;
    Bar bar;
    double member1;
    bool member2;
    unsigned int member3;
    void Swap(Actor& first, Actor& second);
    Actor(const Actor&);
    Actor& operator=(const Actor);
}

Cpp:

void Actor::Swap(Actor& first, Actor& second)
{
    // Swap wont work with my non pointer class
    Bar temp = first.bar;
    first.bar = second.bar;
    second.bar = temp;
    std::swap(first.foo, second.foo);
    std::swap(first.member2, second.member2);
    std::swap(first.member3, second.member3);
}
// What goes here? Is this a correct copy constructor? Does this have anything to do with a copy swap idiom? How can I avoid code duplication in my copy constructor?
Actor::Actor(const Actor& other)
{
    foo = new Foo();
    *foo = *other.foo;
    bar = other.bar;
    member1 = other.member1;
    member2 = other.member2;
    member3 = other.member3;
}
Actor& Actor::operator=(Actor other)
{
    Swap(*this, other);
    return *this;
}

我遵循以下指南:什么是复制-交换习惯用法?

如何避免代码重复?

实际上,CAS背后的主要动机不是为了避免代码重复,而是为了提供强大的异常保证。这意味着,如果您尝试执行复制赋值,而它失败了,那么您尝试赋值的对象不会以任何方式被修改。一般来说,移动构造/赋值不会抛出异常,而复制构造隐式地提供了这种保证,因为如果抛出异常,则对象将不存在。因此,在典型情况下,拷贝赋值是不提供强异常保证或更好的"异类"。

也就是说,强异常保证通常很难提供,并且在实践中可能没有那么有用。我不鼓励人们想当然地认为CAS是最好的。

如果你的目标是避免代码重复,最好的方法是使用零规则:http://en.cppreference.com/w/cpp/language/rule_of_three。基本上,这个想法是为你的类选择单独的成员,这样编译器生成的复制/移动构造函数/赋值就是你想要的行为。我知道这段代码可能是为了理解这些函数而做的练习,但是当你为了自己而不是为了学习而写代码时,最好能意识到这一点。

编辑:最后一点。假设您正在使用c++ 11,一般来说,使用CAS的推荐方法实际上是而不是自己编写swap。相反,您应该编写move构造函数(无论如何都必须这样做)和move赋值。从这两个函数中,通用的std::swap将能够有效地交换您的类。然后您可以在CAS实现中使用通用swap。在某些情况下,您可能自己编写swap,但通常没有必要。更详细的讨论在这里:http://scottmeyers.blogspot.com/2014/06/the-drawbacks-of-implementing-move.html.

复制交换方法是一种实现复制赋值操作符的方法,通过重用交换和复制构造函数来减少重复代码并增加异常安全性。

也就是说我注意到了你的代码:

  • 你没有析构函数,所以当你的Actor对象被析构时,你会泄漏你的Foo* foo;对象。
  • 在你的复制构造函数中,你可以在一个步骤中分配和复制Foo,而不需要分配给中间默认构造的Foo
  • 你的Swap函数是一个接受两个参数的成员,这实际上意味着它得到了三个参数。它应该是一个单参数成员,并与this或两个参数的非成员交换。
  • 为了与标准库保持一致,我建议调用交换函数swap而不是Swap,无论您采用哪种实现方法。
  • 在你的交换中,宁愿删除Bar交换到Bar,而不是在Actor类中实现它:换句话说,让Bar知道如何交换自己并简单地利用该功能。

复制-交换赋值习惯用法需要两种机制:复制c'tor和非抛出交换函数。
因此,你的代码遵循它的字母。

的想法是首先尝试执行不安全操作,您只需通过定义一个新对象来创建一个副本。如果它失败并抛出,你分配给的对象保持不变,它的状态在发生错误时是可预测的。这就是异常安全保证。

如果拷贝时没有出现错误,则执行交换操作来完成赋值。因为swap方法是不抛出的,所以您不会有让您的分配对象处于不一致状态的风险。

最后,临时函数的析构函数清除所有遗留的资源,因为交换方法将这些资源交给了它。

我认为这个实现是很好的,除了一些事情:

1)您忘记在自定义Swap方法中交换member1

2)复制-交换习惯用法通常为operator=提供了强大的异常保证。这意味着它要么在没有改变对象的情况下异常失败,要么成功地执行赋值。在这种情况下,强异常安全性是值得怀疑的,因为Bar赋值可能会抛出异常。

最新更新