提问人:Rafael S. Calsaverini 提问时间:5/6/2011 最后编辑:Rafael S. Calsaverini 更新时间:5/6/2011 访问量:2236
安全转让和复制和交换成语 [已关闭]
Safe assignment and copy-and-swap idiom [closed]
问:
我正在学习 c++,最近我学到了(在堆栈溢出中)关于复制和交换的习惯用语,我对此有一些疑问。因此,假设我有以下类使用复制和交换成语,例如:
class Foo {
private:
int * foo;
int size;
public:
Foo(size_t size) : size(size) { foo = new int[size](); }
~Foo(){delete foo;}
Foo(Foo const& other){
size = other.size;
foo = new int[size];
copy(other.foo, other.foo + size, foo);
}
void swap(Foo& other) {
std::swap(foo, other.foo);
std::swap(size, other.size);
}
Foo& operator=(Foo g) {
g.swap(*this);
return *this;
}
int& operator[] (const int idx) {return foo[idx];}
};
我的问题是,假设我有另一个类,它有一个 Foo 对象作为数据,但没有可能需要自定义复制或赋值的指针或其他资源:
class Bar {
private:
Foo bar;
public:
Bar(Foo foo) : bar(foo) {};
~Bar(){};
Bar(Bar const& other) : bar(other.bar) {};
Bar& operator=(Bar other) {bar = other.bar;}
};
现在我有一系列的问题:
上面为类实现的方法和构造函数是否安全?使用复制和交换后,确保在分配或复制时不会造成任何伤害?
Bar
Foo
Bar
在复制构造函数和 swap 中通过引用传递参数是强制性的吗?
当参数按值传递时,会调用 copy 构造函数来生成对象的临时副本,然后与此副本交换,这是对的吗?如果我通过引用,我会有一个大问题,对吧?
operator=
*this
operator=
在有些情况下,这个成语在复制和分配时无法提供完全的安全性吗?
Foo
答:
应尽可能在初始值设定项列表中初始化类的成员。这也将解决我在评论中告诉您的错误。考虑到这一点,您的代码将变为:
class Foo {
private:
int size;
int * foo;
public:
Foo(size_t size) : size(size), foo(new int[size]) {}
~Foo(){delete[] foo;} // note operator delete[], not delete
Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
copy(other.foo, other.foo + size, foo);
}
Foo& swap(Foo& other) {
std::swap(foo, other.foo);
std::swap(size, other.size);
return *this;
}
Foo& operator=(Foo g) {
return swap(g);
}
int& operator[] (const int idx) {return foo[idx];}
};
和
class Bar {
private:
Foo bar;
public:
Bar(Foo foo) : bar(foo) {};
~Bar(){};
Bar(Bar const& other) : bar(other.bar) { }
Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
Bar& operator=(Bar other) { return swap(other); }
}
自始至终使用相同的成语
注意
正如在对该问题的评论中提到的,的自定义复制构造函数等是不必要的,但我们假设还有其他东西:-)Bar
Bar
第二个问题
需要通过引用传递,因为两个实例都已更改。swap
需要通过引用传递复制构造函数,因为如果按值传递,则需要调用复制构造函数
第三个问题
是的
第四个问题
不,但这并不总是最有效的做事方式
评论
Bar
Bar
operator=
operator=
1 - 上面为 Bar 类实现的方法和构造函数是否安全?对 Foo 使用了复制和交换,确保在分配或复制 Bar 时不会造成任何伤害吗?
关于复制者:这总是安全的(全有或全无)。它要么完成(全部),要么引发异常(无)。
如果类仅由一个成员组成(即没有基类),则赋值运算符将与成员类的运算符一样安全。如果有多个成员,则赋值运算符将不再是“全有或全无”。第二个成员的赋值运算符可能会引发异常,在这种情况下,对象将被赋值为“中途”。这意味着您需要在新类中再次实现复制和交换,以获得“全有或全无”的分配。
但是,从某种意义上说,它仍然是“安全的”,因为您不会泄漏任何资源。当然,每个成员的状态都是一致的——只是新类的状态不会一致,因为一个成员被分配了,而另一个成员没有被分配。
2 - 在复制构造函数和 swap 中通过引用传递参数是强制性的吗?
是的,通过引用传递是强制性的。复制构造函数是复制对象的构造函数,因此它不能按值获取其参数,因为这意味着必须复制参数。这将导致无限递归。(参数将调用 copy-ctor,这意味着为参数调用 copy-ctor,这意味着......对于交换,原因还有另一个原因:如果你要按值传递参数,你永远不能使用 swap 来真正交换两个对象的内容——交换的“目标”将是最初传入的对象的副本,它会立即被销毁。
3 - 当 operator= 的参数按值传递时,会调用 copy 构造函数来生成对象的临时副本,然后用 *this 交换这个副本,这是对的吗?如果我在 operator= 中通过引用传递,我会有一个大问题,对吧?
是的,没错。但是,通过引用 const 来获取参数,构造本地副本,然后与本地副本交换也很常见。然而,by reference-to-const 方法有一些缺点(它禁用了一些优化)。但是,如果您没有实现复制和交换,则可能应该通过对 const 的引用。
4 - 在有些情况下,这个成语在复制和分配 Foo 时无法提供完全的安全性?
我都不知道。当然,人们总是可以通过多线程(如果没有正确同步)使事情失败,但这应该是显而易见的。
评论
foo
std::copy
std::
std::swap
Bar
这是一个典型的例子,说明遵循一个成语会导致不必要的性能损失(过早悲观)。这不是你的错。复制和交换的成语被过度炒作了。这是一个很好的成语。但不应盲目追随。
注意:您可以在计算机上执行的最昂贵的操作之一就是分配和释放内存。在可行的情况下,避免这样做是值得的。
注意:在您的示例中,复制和交换惯用法始终执行一次释放,并且通常(当赋值的 rhs 为左值时)也执行一次分配。
观察:当 时,不需要进行解除分配或分配。你所要做的就是一个.这要快得多。size() == rhs.size()
copy
Foo& operator=(const Foo& g) {
if (size != g.size)
Foo(g).swap(*this);
else
copy(other.foo, other.foo + size, foo);
return *this;
}
即检查可以首先回收资源的情况。然后,如果您无法回收资源,则进行复制和交换(或其他任何操作)。
注意:我的评论与其他人给出的良好答案并不矛盾,也没有任何正确性问题。我的评论只是关于显着的性能损失。
评论
int
上一个:三法则练习意外结果 [重复]
评论
foo = new int[size]
size
std::vector<int>