如何为拷贝交换习语

时间:2016-10-13 19:49:34

标签: c++ copy-constructor

我正在尝试为类实现复制构造函数和赋值运算符。我对复制交换习语感到有些困惑。特别是当涉及到复制构造函数时。复制交换习语是否与复制构造函数有关?如何避免代码重复?

这是我的班级

部首:

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;
}

我正在遵循本指南:What is the copy-and-swap idiom?

4 个答案:

答案 0 :(得分:5)

  

如何避免代码重复?

说实话,复制和交换(CAS)背后的主要动机不是避免代码重复,而是更多地提供强大的异常保证。这意味着如果您尝试执行复制分配,并且失败,则不会以任何方式修改您尝试分配给的对象。一般来说,移动构造/赋值不会抛出,而复制构造会隐式地给出这种保证,因为如果抛出异常而不是对象将不存在。因此,在典型的情况下,复制分配是"奇怪的人"这不提供强有力的例外保证或更好。

尽管如此,强大的异常保证通常难以提供,并且在实践中可能没那么有用。我不鼓励人们自动认为CAS是最好的。

如果您的目标是避免代码重复,最好的方法是使用Zero of Zero:http://en.cppreference.com/w/cpp/language/rule_of_three。基本上,我们的想法是为您的类选择单个成员,以便编译器生成的复制/移动构造函数/赋值是您想要的行为。我知道这段代码可能是一种了解这些功能的练习,但是当你为了学习而编写代码而不是学习时,要注意这一点很好。

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

答案 1 :(得分:4)

复制交换方法是一种在重用交换和复制构造函数方面实现复制赋值运算符以减少重复代码并增加异常安全性的方法。

那就是说我发现了一些关于你代码的事情:

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

答案 2 :(得分:1)

复制和交换赋值习惯用法需要两种机制:复制c'tor和非投掷交换功能。
所以你的代码遵循它的字母。

我们的想法是首先尝试执行不安全的操作,只需定义一个新对象即可创建一个副本。如果它失败并抛出,则您指定的对象保持不变,如果出现错误,则状态是可预测的。这是例外的安全保障。

如果复制时没有发生错误,则执行交换以完成分配。由于交换方法是非抛出的,因此您不会将指定的对象置于不一致状态。

最后,临时的析构函数清除了所有留下的资源,因为swap方法将它们交给它。

答案 3 :(得分:1)

我认为除了以下几点之外,这个实现很好:

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

2)复制和交换习语通常为operator=提供强大的异常保证。这意味着它在没有更改对象或成功执行赋值的情况下失败并发生异常。在您的情况下,强大的异常安全性是值得怀疑的,因为Bar赋值可能会引发异常。