我已经看到了在各个地方推荐的复制和交换习惯用法,作为为赋值运算符实现强异常安全性的推荐/最佳/唯一方法.在我看来,这种方法也有缺点.
考虑以下简化的类似矢量的类,它使用复制和交换:
class IntVec {
size_t size;
int* vec;
public:
IntVec()
: size(0),
vec(0)
{}
IntVec(IntVec const& other)
: size(other.size),
vec(size? new int[size] : 0)
{
std::copy(other.vec, other.vec + size, vec);
}
void swap(IntVec& other) {
using std::swap;
swap(size, other.size);
swap(vec, other.vec);
}
IntVec& operator=(IntVec that) {
swap(that);
return *this;
}
//~IntVec() and other functions ...
}
Run Code Online (Sandbox Code Playgroud)
通过复制构造函数实现赋值可能是有效的并且可以保证异常安全,但是它也可能导致不必要的分配,甚至可能导致无内存错误.
考虑分配700MB的情况下IntVec
,以一个1GB IntVec
以<2GB堆限制的机器上.最佳分配将意识到它已经分配了足够的内存,并且只将数据复制到已经分配的缓冲区中.复制和交换实现将导致在释放1GB缓冲区之前分配另一个700MB缓冲区,导致所有3个缓冲区同时尝试在内存中共存,这将不必要地抛出内存不足错误.
这种实现可以解决问题:
IntVec& operator=(IntVec const& that) {
if(that.size <= size) {
size = that.size;
std::copy(that.vec, that.vec + that.size, vec);
} else
swap(IntVec(that));
return *this;
}
Run Code Online (Sandbox Code Playgroud)
所以底线是:
我是对的,这是复制和交换习惯用法的问题,或者正常的编译器优化以某种方式消除额外的分配,或者我忽略了一些问题与我的"更好"版本的副本 -和交换一个解决,或者我在做我的数学/算法错误,问题不存在?
650*_*502 15
重用空间的实现存在两个问题
如果你要分配very_huge_vect = very_small_vect;
额外的内存将不会被释放.这可能是你想要的,也可能不是.
在整数的情况下,一切都很好,但复制操作可能引发异常的对象呢?你最终会得到一个混乱的数组,其中部分副本已经完成并且已被截断.如果复制操作失败(交换习惯做什么),更好的办法是保持目标不受影响.
顺便说一句,作为一般规则,在极少数情况下,您可以找到任何看起来像"永远是最佳解决方案"的东西.如果你正在寻找一颗银弹,那么编程就不是正确的选择.
Yak*_*ont 12
要解决您的特定问题,请将copy-swap修改为clear-copy-swap.
这可以通过以下方式进行:
Foo& operator=( Foo const& o ) {
using std::swap;
if (this == &o) return *this; // efficient self assign does nothing
swap( *this, Foo{} ); // generic clear
Foo tmp = o; // copy to a temporary
swap( *this, tmp ); // swap temporary state into us
return *this;
}
Foo& operator=( Foo && o ) {
using std::swap;
if (this == &o) return *this; // efficient self assign does nothing
swap( *this, Foo{} ); // generic clear
Foo tmp = std::move(o); // move to a temporary
swap( *this, tmp ); // swap temporary state into us
return *this;
}
Run Code Online (Sandbox Code Playgroud)
虽然这确实会导致大量分配发生,但是在发生大量释放后会立即发生.
copy-swap的关键部分是它实现了正确的实现,并且获得异常安全的副本权限非常棘手.
你会注意到,如果抛出异常,上面的结果是lhs可能处于空状态.相比之下,标准的copy-swap将产生有效的副本,或lhs不变.
现在,我们可以做一个最后的小技巧.假设我们的状态是在一个vector
子状态中捕获的,那些子状态具有异常安全swap
或move
.然后我们可以:
Foo& move_substates( Foo && o ) {
if (this == &o) return *this; // efficient self assign does nothing
substates.resize( o.substates.size() );
for ( unsigned i = 0; i < substates.size(); ++i) {
substates[i] = std::move( o.substates[i] );
}
return *this;
}
Run Code Online (Sandbox Code Playgroud)
它模仿你的副本内容,但是move
代替它copy
.然后我们可以在以下方面利用它operator=
:
Foo& operator=( Foo && o ) {
using std::swap;
if (this == &o) return *this; // efficient self assign does nothing
if (substates.capacity() >= o.substates.size() && substates.capacity() <= o.substates.size()*2) {
return move_substates(std::move(o));
} else {
swap( *this, Foo{} ); // generic clear
Foo tmp = std::move(o); // move to a temporary
swap( *this, tmp ); // swap temporary state into us
return *this;
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们重用内部内存并避免分配,如果我们从源代码移动,如果你害怕内存分配,我们不会比源代码大得多.
Zan*_*ynx 11
是的,内存不足是一个潜在的问题.
您已经知道复制和交换解决了什么问题.这是你"撤消"失败的任务的方式.
使用更有效的方法,如果在流程的某个阶段分配失败,则无法返回.而且,如果一个对象编写得不好,失败的赋值甚至可能使对象损坏,并且程序在对象销毁期间会崩溃.
我是对的,这是复制和交换习语的问题
这取决于; 如果你有这么大的载体,那么是的,你是对的.
我是否忽略了复制交换解决的"更好"版本的问题
你正在优化罕见的情况.为什么要执行额外的检查并为代码添加圈复杂度?(除非当然,在您的应用中有太大的载体是常态)
从C++ 11开始,临时数的r值可以通过c ++来收获.所以通过const&
抛弃优化来传递你的论点.
底线:这个词总是容易被反驳.如果有一个通用解决方案,总是优于任何替代方案,我想编译器可以采用它并隐式生成基于此的默认赋值运算符
在前面的注释中,隐式声明的复制赋值运算符具有表单
T& T::operator=(const T&);
Run Code Online (Sandbox Code Playgroud)
通过const&
而不是通过值接受其参数(如在复制和交换习语中)
归档时间: |
|
查看次数: |
1795 次 |
最近记录: |