我试图实现一个类的复制构造函数和赋值操作符。我对拷贝交换习语有点困惑了。特别是当涉及到复制构造函数时。拷贝交换习惯用法与复制构造函数有任何关系吗?如何避免代码重复?
这是我的类
头: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
赋值可能会抛出异常。