在此代码中:
template<class T>
void Stack<T>::Push( const T& t )
{
if( vused_ == vsize_ ) // grow if necessary
{
size_t vsize_new = vsize_*2+1; // by some grow factor
T* v_new = NewCopy( v_, vsize_, vsize_new );
delete[] v_; // this can't throw
v_ = v_new; // take ownership
vsize_ = vsize_new;
}
v_[vused_] = t;
vused++;
}
我们尝试异常安全且异常中立。它是通过具有NewCopy()
辅助函数(复制指向内存并返回指向复制值的指针)的方法实现的,这些函数遵循这些原则(我们对此函数不感兴趣,它是异常安全和中性的)。最后,所有工作都是因为
v_[vused_] = t;
vused++;
如果赋值不抛出,我们只改变Stack的状态。如果我们写了
v_[vused_++] = t;
会违反异常安全吗?我的猜测是肯定的(postincrement运算符返回旧值,但确实在返回之前增加变量,然后在返回后执行赋值,因此在异常的情况下对象状态是无效的)。但我可能会弄错(?)
答案 0 :(得分:2)
从标准,1.9 / 15:
当调用函数时(无论函数是否为内联函数),在执行每个表达式或语句之前,对与任何参数表达式或指定被调用函数的后缀表达式相关联的每个值计算和副作用进行排序。被调用函数的主体。
所以当我们打电话时:
v_[vused_++]
也就是说:
*(v_ + vused_++)
在取消引用之前,对postincrement运算符进行排序。因此,无论该调用中发生了什么,即使它抛出,vused_
也会递增。因此,如果随后的分配抛出将vused_
递增,这将违反强异常保证。这很容易说服自己:
void foo(int ) { throw std::runtime_error(""); }
int main() {
int ctr = 0;
try {
foo(ctr++);
}
except(...) { }
std::cout << ctr << std::endl; // prints 1
}
但如果我们打电话给foo(ctr); ctr++
,它就会打印0。
答案 1 :(得分:1)
C ++标准版n3337 § 8.3.4 / 6阵列
注意:除了为类(13.5.5)声明它之外, 下标operator []的解释方式是E1 [E2] 与*((E1)+(E2))相同。由于适用的转换规则 to +,如果E1是一个数组而E2是一个整数,那么E1 [E2]指的是 E1的E2成员。因此,尽管外观不对称, 下标是一种可交换的操作。
因此
v_[vused_++] = t;
与
没有区别*((v_)+(vused_++)) = t;
所以很明显++
将在赋值之前进行评估,如果T::operator=
抛出异常,则该对象的状态无效。
答案 2 :(得分:0)
鉴于NewCopy支持强保证,因此您拥有的代码是异常安全的。
同意@Barry,你提出的改变将使其异常 - 不安全(并且不再提供性能)。
再次
为什么你在编写自己的堆栈然后标准库包含一个完全优化的,异常安全的实现?