Code Exception Safe Functions

The hard part about writing exception safe code isn't the throwing or catching of exceptions; it's everything in between. As a thrown exception wends its way from the throw expression to the catch clause, every partially executed function on that path must "clean up" any important resources that it controls before its activation record is popped off the execution stack. Generally (but not always), all that is required to write an exception safe function is a moment's reflection and some common sense.

For example, consider the implementation of String assignment from Assignment and Initialization Are Different [12, 41]:

String &String::operator =( const char *str ) {
    if( !str ) str = "";
    char *tmp = strcpy( new char[ strlen(str)+1 ], str );
    delete [] s_;
    s_ = tmp;
    return *this;
}

The implementation of this function may look superfluously ornate, since we could have coded it with fewer lines and no temporary:

String &String::operator =( const char *str ) {
    delete [] s_;                                        
    if( !str ) str = "";
    s_ = strcpy( new char[ strlen(str)+1 ], str );
    return *this;
}

However, while the array delete comes with a social guarantee not to throw an exception (see Exception Safety Axioms [38, 131]), the array new a couple of lines later makes no such promise. If we delete the old buffer before we know whether allocation of the new buffer will succeed, we'll leave the String object in a bad state. Herb Sutter summarizes the situation well in his Exceptional C++, which I'll paraphrase as this: First do anything that could cause an exception "off to the side" without changing important state, and then use operations that can't throw an exception to finish up. That's what we did in the first implementation of String::operator = above. Let's look at another example from Commands and Hollywood [19, 67]:

void Button::setAction( const Action *newAction ) {
    Action *temp = newAction->clone(); // off to the side...
    delete action_; // then change state!
    action_ = temp;
}

Because it's a virtual function, we really know nothing about the exception-related behavior of the call to clone, so we assume the worst. If the clone operation succeeds, we continue with an exception safe deletion and pointer assignment. Otherwise, a thrown exception from clone will cause premature exit from Button::setAction with no harm done. Newer C++ programmers have a tendency to "clean up" code like this in such a way as to make it exception unsafe:

void Button::setAction( const Action *newAction ) {              
    delete action_; // change state!                             
    action_ = newAction->clone(); // then maybe throw?           
}                                                                

Performing the deletion (which is assumed to be exception safe) before the clone (which makes no such promise) will leave the Button object in an inconsistent state if clone throws an exception.

Notice that properly written exception safe code employs relatively few try blocks. A novice attempt to write exception safe code is often littered with unnecessary and often damaging trys and catches:

void Button::setAction( const Action *newAction ) {              
    delete action_;                                              
    try {                                                        
        action_ = newAction->clone();                            
    }                                                            
    catch( ... ) {                                               
        action_ = 0;                                             
        throw;                                                   
    }                                                            
}                                                                

This version with its fussy try block and catch clause is exception safe in the sense that the Button object is left in a consistent (but likely different) state if clone throws an exception. However, our previous version was shorter, simpler, and more exception safe because it left the Button object not merely consistent but unchanged.

It's a good idea to minimize the use of try blocks, where possible, and employ them primarily in locations where you really want to examine the type of a passing exception in order to do something with it. In practice, these locations are often at module boundaries between your code and third-party libraries and between your code and the operating system.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值