Exception Safety issue

Edit

问题描述

Evaluation Orders and Disorders

// Example 1(a)
//
f( expr1, expr2 );
// Example 1(b)
//
f( g( expr1 ), h( expr2 ) );

编译器只要满足下面的规则,为了便于优化执行顺序,而C++标准并不要求具体指令顺序:

  1. expr1在g执行之前完成,expr2在h执行之前完成
  2. g, h在f执行之前完成

首先,在函数正式执行之前,所有的Expression Evaluation要完成。
其次,有可能出现乱序。
所以有可能出现下面的执行顺序:
顺序一

  • expr1
  • expr2
  • g
  • h

顺序二

  • expr1
  • g
  • expr2
  • h

上面两种情况都符合。

Exception Safety Problems

当C++编译器调用new操作符的时候会做两件事:1. 开辟内存,2. 调用类构造函数。所以下面的code如果处理得当,是不会有内存泄漏:

T1* t1 = new T1;
T2* t2 = new T2;

当T1构造函数出现异常时,相应的内存会被释放掉。当T2构造函数出现异常时,T2的内存会被释放,T1的内存也有机会被释放。(解决方法是在两条语句之间加上异常处理,当然用smart pointer也可以解决问题。后面会阐述smart pointer的方法)

但是在遇到Expression Evaluation的时候,情况会变得复杂:

// In some header file:
void f( T1*, T2* );
// In some implementation file:
f( new T1, new T2 );
  • T2构造函数异常时,根本没有机会释放T1的内存。用异常处理也没办法,因为T1的内存开辟出来赋值给了一个临时变量。而这个变量的有效期就只在那个括号里。
  • 乱序让问题更加复杂,因为你不知道是T1先构造还是T2先构造,你不知道是下面的哪种顺序,但是每一种顺序都有问题。所以无从下手。
    • 顺序一
      • 1: allocate memory for T1
        2: construct T1
        3: allocate memory for T2
        4: construct T2
        5: call f()
    • 顺序二
      • 1: allocate memory for T1
        2: allocate memory for T2
        3: construct T1
        4: construct T2
        5: call f()

解决方案

看起来像的解决方案

用auto_ptr

// In some header file:
void f( auto_ptr<T1>, auto_ptr<T2> );
// In some implementation file:
f( auto_ptr<T1>( new T1 ), auto_ptr<T2>( new T2 ) );

看起来用了smart pointer,应该不存在释放内存的问题了。但其实并没有改善,因为乱序问题,你没法保证auto_ptr<T1>new T2之前调用。如果T2构造失败了,new T1的内存就泄露了。Vice versa。

用参数默认值

// In some header file:
void f( auto_ptr<T1> = auto_ptr<T1>( new T1 ),
auto_ptr<T2> = auto_ptr<T1>( new T2 ) );
// In some implementation file:
f();

没有本质的改善。因为虽然参数默认值是在函数声明中,但是那些newauto_ptr<>是在执行时才会去做的事情。一样会有乱序。

真正的解决方案

// In some header file (same as in Example 2b):
void f( auto_ptr<T1>, auto_ptr<T2> );
// In some implementation file:
f( auto_ptr_new<T1>(), auto_ptr_new<T2>() );

看起来和之前的假解决方案很像对不对,区别是相应的newauto_ptr<>构造函数放到一起了。这样不管是auto_ptr_new<T1>()在前,还是auto_ptr_new<T2>()在前,都不会引起Exception Safety问题。

这个方案也就是网上经常有人问到的“为什么要用make_xxx_ptr,而不用new xxx_ptr?”的原因之一,xxx_ptr可以是任何的smart pointer(包括但不限于:auto_ptr, shared_ptr, unique_ptr):

  • 可以避免Exception Safety问题
  • 避免显示使用new运算符,避免潜在问题
  • 局限是只能用默认构造函数,而无法使用带参数构造函数

下面也是一个办法:

// In some header file:
void f( auto_ptr<T1>, auto_ptr<T2> );
// In some implementation file:
{
auto_ptr<T1> t1( new T1 );
f( t1, auto_ptr<T2>( new T2 ) );
}

这个方法更好:

// In some header file:
void f( auto_ptr<T1>, auto_ptr<T2> );
// In some implementation file:
{
auto_ptr<T1> t1( new T1 );
auto_ptr<T2> t2( new T2 );
f( t1, t2 );
}

总结

Exception-Safe Function Calls作者的guide lines作总结:

Perform every resource allocation (e.g., new) in its own code statement which immediately gives the new resource to a manager object (e.g., auto_ptr).

意思是一行就只分配一个资源,并且立即将该资源交给smart pointer管理。

参考文献

Exception-Safe Function Calls
GotW #89 Solution: Smart Pointer

%23%20Exception%20Safety%20issue%0A@%28myblog%29%5Bc/c++%5D%0A%0A%23%23%20%u95EE%u9898%u63CF%u8FF0%0A%23%23%23%20Evaluation%20Orders%20and%20Disorders%0A%60%60%60%0A//%20%20Example%201%28a%29%0A//%0Af%28%20expr1%2C%20expr2%20%29%3B%0A//%20%20Example%201%28b%29%0A//%0Af%28%20g%28%20expr1%20%29%2C%20h%28%20expr2%20%29%20%29%3B%0A%60%60%60%0A%3E%20%u7F16%u8BD1%u5668%u53EA%u8981%u6EE1%u8DB3%u4E0B%u9762%u7684%u89C4%u5219%uFF0C%u4E3A%u4E86%u4FBF%u4E8E%u4F18%u5316%u6267%u884C%u987A%u5E8F%uFF0C%u800CC++%u6807%u51C6%u5E76%u4E0D%u8981%u6C42%u5177%u4F53%u6307%u4EE4%u987A%u5E8F%uFF1A%0A%3E%201.%20expr1%u5728g%u6267%u884C%u4E4B%u524D%u5B8C%u6210%uFF0Cexpr2%u5728h%u6267%u884C%u4E4B%u524D%u5B8C%u6210%0A%3E%202.%20g%2C%20h%u5728f%u6267%u884C%u4E4B%u524D%u5B8C%u6210%0A%0A%u9996%u5148%uFF0C%u5728%u51FD%u6570%u6B63%u5F0F%u6267%u884C%u4E4B%u524D%uFF0C%u6240%u6709%u7684Expression%20Evaluation%u8981%u5B8C%u6210%u3002%0A%u5176%u6B21%uFF0C%u6709%u53EF%u80FD%u51FA%u73B0%u4E71%u5E8F%u3002%0A%u6240%u4EE5%u6709%u53EF%u80FD%u51FA%u73B0%u4E0B%u9762%u7684%u6267%u884C%u987A%u5E8F%uFF1A%0A**%u987A%u5E8F%u4E00**%0A-%20expr1%0A-%20expr2%0A-%20g%0A-%20h%0A%0A**%u987A%u5E8F%u4E8C**%0A-%20expr1%0A-%20g%0A-%20expr2%0A-%20h%0A%0A%u4E0A%u9762%u4E24%u79CD%u60C5%u51B5%u90FD%u7B26%u5408%u3002%0A%0A%23%23%23%20Exception%20Safety%20Problems%0A%u5F53C++%u7F16%u8BD1%u5668%u8C03%u7528new%u64CD%u4F5C%u7B26%u7684%u65F6%u5019%u4F1A%u505A%u4E24%u4EF6%u4E8B%uFF1A1.%20%u5F00%u8F9F%u5185%u5B58%uFF0C2.%20%u8C03%u7528%u7C7B%u6784%u9020%u51FD%u6570%u3002%u6240%u4EE5%u4E0B%u9762%u7684code%u5982%u679C%u5904%u7406%u5F97%u5F53%uFF0C%u662F%u4E0D%u4F1A%u6709%u5185%u5B58%u6CC4%u6F0F%uFF1A%0A%60%60%60%0AT1*%20t1%20%3D%20new%20T1%3B%0AT2*%20t2%20%3D%20new%20T2%3B%0A%60%60%60%0A%u5F53T1%u6784%u9020%u51FD%u6570%u51FA%u73B0%u5F02%u5E38%u65F6%uFF0C%u76F8%u5E94%u7684%u5185%u5B58%u4F1A%u88AB%u91CA%u653E%u6389%u3002%u5F53T2%u6784%u9020%u51FD%u6570%u51FA%u73B0%u5F02%u5E38%u65F6%uFF0CT2%u7684%u5185%u5B58%u4F1A%u88AB%u91CA%u653E%uFF0CT1%u7684%u5185%u5B58%u4E5F%u6709%u673A%u4F1A%u88AB%u91CA%u653E%u3002%uFF08%u89E3%u51B3%u65B9%u6CD5%u662F%u5728%u4E24%u6761%u8BED%u53E5%u4E4B%u95F4%u52A0%u4E0A%u5F02%u5E38%u5904%u7406%uFF0C%u5F53%u7136%u7528smart%20pointer%u4E5F%u53EF%u4EE5%u89E3%u51B3%u95EE%u9898%u3002%u540E%u9762%u4F1A%u9610%u8FF0smart%20pointer%u7684%u65B9%u6CD5%uFF09%0A%0A%u4F46%u662F%u5728%u9047%u5230Expression%20Evaluation%u7684%u65F6%u5019%uFF0C%u60C5%u51B5%u4F1A%u53D8%u5F97%u590D%u6742%uFF1A%0A%60%60%60%0A//%20%20In%20some%20header%20file%3A%0Avoid%20f%28%20T1*%2C%20T2*%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0Af%28%20new%20T1%2C%20new%20T2%20%29%3B%0A%60%60%60%0A-%20T2%u6784%u9020%u51FD%u6570%u5F02%u5E38%u65F6%uFF0C%u6839%u672C%u6CA1%u6709%u673A%u4F1A%u91CA%u653ET1%u7684%u5185%u5B58%u3002%u7528%u5F02%u5E38%u5904%u7406%u4E5F%u6CA1%u529E%u6CD5%uFF0C%u56E0%u4E3AT1%u7684%u5185%u5B58%u5F00%u8F9F%u51FA%u6765%u8D4B%u503C%u7ED9%u4E86%u4E00%u4E2A%u4E34%u65F6%u53D8%u91CF%u3002%u800C%u8FD9%u4E2A%u53D8%u91CF%u7684%u6709%u6548%u671F%u5C31%u53EA%u5728%u90A3%u4E2A%u62EC%u53F7%u91CC%u3002%0A-%20%u4E71%u5E8F%u8BA9%u95EE%u9898%u66F4%u52A0%u590D%u6742%uFF0C%u56E0%u4E3A%u4F60%u4E0D%u77E5%u9053%u662FT1%u5148%u6784%u9020%u8FD8%u662FT2%u5148%u6784%u9020%uFF0C%u4F60%u4E0D%u77E5%u9053%u662F%u4E0B%u9762%u7684%u54EA%u79CD%u987A%u5E8F%uFF0C%u4F46%u662F%u6BCF%u4E00%u79CD%u987A%u5E8F%u90FD%u6709%u95EE%u9898%u3002%u6240%u4EE5%u65E0%u4ECE%u4E0B%u624B%u3002%0A%09-%20%u987A%u5E8F%u4E00%0A%09%09-%201%3A%20allocate%20memory%20for%20T1%0A2%3A%20construct%20T1%0A3%3A%20allocate%20memory%20for%20T2%0A4%3A%20construct%20T2%0A5%3A%20call%20f%28%29%0A%09-%20%u987A%u5E8F%u4E8C%0A%09%09-%201%3A%20allocate%20memory%20for%20T1%0A2%3A%20allocate%20memory%20for%20T2%0A3%3A%20construct%20T1%0A4%3A%20construct%20T2%0A5%3A%20call%20f%28%29%0A%0A%0A%23%23%20%u89E3%u51B3%u65B9%u6848%0A%23%23%23%20%u770B%u8D77%u6765%u50CF%u7684%u89E3%u51B3%u65B9%u6848%0A%23%23%23%23%20%u7528auto_ptr%0A%60%60%60%0A//%20%20In%20some%20header%20file%3A%0Avoid%20f%28%20auto_ptr%3CT1%3E%2C%20auto_ptr%3CT2%3E%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0Af%28%20auto_ptr%3CT1%3E%28%20new%20T1%20%29%2C%20auto_ptr%3CT2%3E%28%20new%20T2%20%29%20%29%3B%0A%60%60%60%0A%u770B%u8D77%u6765%u7528%u4E86smart%20pointer%uFF0C%u5E94%u8BE5%u4E0D%u5B58%u5728%u91CA%u653E%u5185%u5B58%u7684%u95EE%u9898%u4E86%u3002%u4F46%u5176%u5B9E%u5E76%u6CA1%u6709%u6539%u5584%uFF0C%u56E0%u4E3A%u4E71%u5E8F%u95EE%u9898%uFF0C%u4F60%u6CA1%u6CD5%u4FDD%u8BC1%60auto_ptr%3CT1%3E%60%u5728%60new%20T2%60%u4E4B%u524D%u8C03%u7528%u3002%u5982%u679CT2%u6784%u9020%u5931%u8D25%u4E86%uFF0C%60new%20T1%60%u7684%u5185%u5B58%u5C31%u6CC4%u9732%u4E86%u3002Vice%20versa%u3002%0A%0A%23%23%23%23%20%u7528%u53C2%u6570%u9ED8%u8BA4%u503C%0A%60%60%60%0A//%20%20In%20some%20header%20file%3A%0Avoid%20f%28%20auto_ptr%3CT1%3E%20%3D%20auto_ptr%3CT1%3E%28%20new%20T1%20%29%2C%0A%20%20%20%20%20%20%20%20auto_ptr%3CT2%3E%20%3D%20auto_ptr%3CT1%3E%28%20new%20T2%20%29%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0Af%28%29%3B%0A%60%60%60%0A%u6CA1%u6709%u672C%u8D28%u7684%u6539%u5584%u3002%u56E0%u4E3A%u867D%u7136%u53C2%u6570%u9ED8%u8BA4%u503C%u662F%u5728%u51FD%u6570%u58F0%u660E%u4E2D%uFF0C%u4F46%u662F%u90A3%u4E9B%60new%60%u548C%60auto_ptr%3C%3E%60%u662F%u5728%u6267%u884C%u65F6%u624D%u4F1A%u53BB%u505A%u7684%u4E8B%u60C5%u3002%u4E00%u6837%u4F1A%u6709%u4E71%u5E8F%u3002%0A%0A%23%23%23%20%u771F%u6B63%u7684%u89E3%u51B3%u65B9%u6848%0A%60%60%60%0A//%20%20In%20some%20header%20file%20%28same%20as%20in%20Example%202b%29%3A%0Avoid%20f%28%20auto_ptr%3CT1%3E%2C%20auto_ptr%3CT2%3E%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0Af%28%20auto_ptr_new%3CT1%3E%28%29%2C%20auto_ptr_new%3CT2%3E%28%29%20%29%3B%0A%60%60%60%0A%u770B%u8D77%u6765%u548C%u4E4B%u524D%u7684%u5047%u89E3%u51B3%u65B9%u6848%u5F88%u50CF%u5BF9%u4E0D%u5BF9%uFF0C%u533A%u522B%u662F%u76F8%u5E94%u7684%60new%60%u548C%60auto_ptr%3C%3E%60%u6784%u9020%u51FD%u6570%u653E%u5230%u4E00%u8D77%u4E86%u3002%u8FD9%u6837%u4E0D%u7BA1%u662F%60auto_ptr_new%3CT1%3E%28%29%60%u5728%u524D%uFF0C%u8FD8%u662F%60auto_ptr_new%3CT2%3E%28%29%60%u5728%u524D%uFF0C%u90FD%u4E0D%u4F1A%u5F15%u8D77Exception%20Safety%u95EE%u9898%u3002%0A%3E%u8FD9%u4E2A%u65B9%u6848%u4E5F%u5C31%u662F%u7F51%u4E0A%u7ECF%u5E38%u6709%u4EBA%u95EE%u5230%u7684%u201C%u4E3A%u4EC0%u4E48%u8981%u7528make%5C_xxx_ptr%uFF0C%u800C%u4E0D%u7528new%20xxx%5C_ptr%3F%u201D%u7684%u539F%u56E0%u4E4B%u4E00%uFF0Cxxx%5C_ptr%u53EF%u4EE5%u662F%u4EFB%u4F55%u7684smart%20pointer%uFF08%u5305%u62EC%u4F46%u4E0D%u9650%u4E8E%uFF1Aauto_ptr%2C%20shared_ptr%2C%20unique_ptr%uFF09%uFF1A%0A%3E-%20%u53EF%u4EE5%u907F%u514DException%20Safety%u95EE%u9898%0A%3E-%20%u907F%u514D%u663E%u793A%u4F7F%u7528%60new%60%u8FD0%u7B97%u7B26%uFF0C%u907F%u514D%u6F5C%u5728%u95EE%u9898%0A%3E-%20%u5C40%u9650%u662F%u53EA%u80FD%u7528%u9ED8%u8BA4%u6784%u9020%u51FD%u6570%uFF0C%u800C%u65E0%u6CD5%u4F7F%u7528%u5E26%u53C2%u6570%u6784%u9020%u51FD%u6570%0A%0A%u4E0B%u9762%u4E5F%u662F%u4E00%u4E2A%u529E%u6CD5%uFF1A%0A%60%60%60%0A//%20%20In%20some%20header%20file%3A%0Avoid%20f%28%20auto_ptr%3CT1%3E%2C%20auto_ptr%3CT2%3E%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0A%7B%0A%20%20auto_ptr%3CT1%3E%20t1%28%20new%20T1%20%29%3B%0A%20%20f%28%20t1%2C%20auto_ptr%3CT2%3E%28%20new%20T2%20%29%20%29%3B%0A%7D%0A%60%60%60%0A%u8FD9%u4E2A%u65B9%u6CD5%u66F4%u597D%uFF1A%0A%60%60%60%0A//%20%20In%20some%20header%20file%3A%0Avoid%20f%28%20auto_ptr%3CT1%3E%2C%20auto_ptr%3CT2%3E%20%29%3B%0A//%20%20In%20some%20implementation%20file%3A%0A%7B%0A%20%20auto_ptr%3CT1%3E%20t1%28%20new%20T1%20%29%3B%0A%20%20auto_ptr%3CT2%3E%20t2%28%20new%20T2%20%29%3B%0A%20%20f%28%20t1%2C%20t2%20%29%3B%0A%7D%0A%60%60%60%0A%0A%23%23%20%u603B%u7ED3%0A%u4EE5%5BException-Safe%20Function%20Calls%5D%28http%3A//www.gotw.ca/gotw/056.htm%29%u4F5C%u8005%u7684guide%20lines%u4F5C%u603B%u7ED3%uFF1A%0A%3EPerform%20every%20resource%20allocation%20%28e.g.%2C%20new%29%20in%20its%20own%20code%20statement%20which%20immediately%20gives%20the%20new%20resource%20to%20a%20manager%20object%20%28e.g.%2C%20auto_ptr%29.%0A%0A%u610F%u601D%u662F%u4E00%u884C%u5C31%u53EA%u5206%u914D%u4E00%u4E2A%u8D44%u6E90%uFF0C%u5E76%u4E14%u7ACB%u5373%u5C06%u8BE5%u8D44%u6E90%u4EA4%u7ED9smart%20pointer%u7BA1%u7406%u3002%0A%0A%23%23%20%u53C2%u8003%u6587%u732E%0A%5BException-Safe%20Function%20Calls%5D%28http%3A//www.gotw.ca/gotw/056.htm%29%0A%5BGotW%20%2389%20Solution%3A%20Smart%20Pointer%5D%28https%3A//herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/%29