#8 具有挑战性的话题:异常安全(exception safety)
难度:9/10
c++的异常机制是解决某些问题的很好的方法,但它引入了许多隐藏的控制流程,难以使用。尝试自己实现一个非常简单的容器(一个可以push和pop的stack),看看要达到异常安全(exception-safe)和异常中立(exception-neutral)需要涉及哪些问题。
问题:
1.实现下面的容器,要求是异常中立的(exception-neutral)。stack对象必须始终状态一致而且即使在内部操作抛出异常时也是可析构的,而且应该允许t内部抛出的异常传递到调用者。
template <class t>
// t must have default ctor and copy assignment
class stack
{
public:
stack();
~stack();
stack(const stack&);
stack& operator=(const stack&);
unsigned count(); // returns # of t's in the stack
void push(const t&);
t pop(); // if empty, returns default-
// constructed t
private:
t* v_; // pointer to a memory area big
// enough for 'vsize_' t objects
unsigned vsize_; // the size of the 'v_' area
unsigned vused_; // the number of t's actually
// used in the 'v_' area
};
附加题:
2.根据目前的标准草案,标准库中的容器是异常安全(exception-safe)或异常中立(exception-neutral)的吗?[译者注:此期gotw问题在1997年4月提出]
3.容器是否应该是异常中立(exception-neutral)的?为什么?折衷办法是什么?
4.容器是否应该使用异常声明?例如,我们是否应该声明"stack::stack()throw(bad_alloc);"?
挑战:
对于很多现行的编译器,使用"try"和"catch"经常给你的程序增加额外负担,在这种低层次的可重用的容器中最好避免使用。你是否能够实现所有stack的成员函数使之既能够满足上述要求,又不使用"try"和"catch"?
****************************************
这儿有两个示例函数(但是并不能完全满足上述要求)可以参考:
template<class t>
stack<t>::stack()
: v_(0),
vsize_(10),
vused_(0)
{
v_ = new t[vsize_]; // initial allocation
}
template<class t>
t stack<t>::pop()
{
t result; // if empty, return default-constructed t
if( vused_ > 0)
{
result = v_[--vused_];
}
return result;
}
解答
[这个解答现在看来并不是完全正确,更新并作了很多扩充的解答,请看我在1997年9月和10/11月的c++ report杂志上发表的文章,比此更深入的讨论请参看我的"exceptional c++"]
重要说明:我并不声称下面的解答满足所有的上述要求。实际上,我还没有找到编译器能够编译通过。虽然我把我能够想到的所有的各种相互作用和影响都提出来了,但这个练习的主要目的是为了说明写出异常安全的代码需要程序员非常的小心。
另外,可以参看tom cargill的一篇非常优秀的文章:"exception handling: a false sense of security"( c++ report,vol.9 no.6,nov-dec 1994).他展示了异常处理是非常需要技巧的,但请注意他的文章并不是主张完全摒弃异常处理,只是告诉人们使用异常机制需要非常的小心。
最后一个说明:为了使解答更加简单,我决定不采用用基类的方法来解决异常安全的资源归属(resource ownership)问题。我将邀请dave abrahams(或者其他人)继续我们的解答并展示这个非常有效的方法。
回顾我们的问题,下面是需要实现的接口:
template <class t>
// t must have default ctor and copy assignment
class stack
{
public:
stack();
~stack();
stack(const stack&);
stack& operator=(const stack&);
unsigned count(); // returns # of t's in the stack