Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

Writing an exception safe code in generic way.

Rate me:
Please Sign up or sign in to vote.
4.88/5 (11 votes)
12 Dec 20069 min read 40.7K   324   36   5
An article on how to change the way of writing exception-safe code.

Table of Contents

  1. Introduction
  2. Basic usage
    1. Mating a host function call with a mate function
    2. Composing a mate function object
    3. Mating with a condition
    4. am::mate<void> specialization
  3. Utility classes and their helper template functions
    1. Supporting parameter by reference: am::ref and am::cref
    2. Free function pointer: am::ptr_fun
    3. Bind the 1st or the 2nd argument of the binary function: am::bind1st and am::bind2nd
    4. Miscellaneous
  4. Use am::lambda to compose a function object
  5. References

Introduction

Here is an example of a traditional scenario in our daily programming as shown below. We are prone to forget to call some clean up codes when the function has multiple return paths or an exception is thrown in the middle of very complicated construct.

C++
void test01()
{
 char const * lpszFilePath = "c:\\test.dat";

 HANDLE hFile( ::CreateFile( lpszFilePath, GENERIC_WRITE, 0, NULL,
                       OPEN_ALWAYS, 0, NULL ) );

 if( INVALID_HANDLE_VALUE != hFile )
 {

   if( 100 > ::GetFileSize( hFile, NULL ) )
     throw "File size too small!";

   ::CloseHandle( hFile );
 }

 ::DeleteFile( lpszFilePath );
}

boost::shared_ptr can be used to transform the code snippet above in an exception safe way. It purely depends on the ability of the boost::shared_ptr that it allows for the user to provide a custom deleter function object. See more details here.

C++
#include <boost/shared_ptr.hpp>

#define BOOST_BIND_ENABLE_STDCALL
#include <boost/bind.hpp>

void test02()
{
  boost::shared_ptr<char const> spFilePath( "c:\\test.dat", &::DeleteFile );

  boost::shared_ptr<void> spFile(
   ::CreateFile( spFilePath.get(), GENERIC_WRITE, 0, NULL,
                           OPEN_ALWAYS, 0, NULL ),
   &::CloseHandle ); // Custom deleter.

  if( INVALID_HANDLE_VALUE != spFile.get() )
  {

    if( 100 > ::GetFileSize( spFile.get(), NULL ) )
      throw "File size too small!";
  }
}

Writing a code in an exception safe way at the object level doesn't require the fancy reference counting feature of boost::shared_ptr, but only requires that of the custom deleter function. Speaking accurately, what we need is an ability to call an arbitrary custom function object in the destructor of our class variable when it goes out of scope. By extracting and combining merits of the existing implementations such as boost::shared_ptr, ScopeGuard and etc., here I would like to introduce a small am::mate utility class.

C++
#include "mate.hpp"

#define AM_BIND_ENABLE_STDCALL
#include "lambda.hpp"

void test03()
{
  am::mate<char const *> lpszFilePath( "c:\\test.dat", &::DeleteFile );

  am::mate<HANDLE> hFile(
   ::CreateFile( lpszFilePath, GENERIC_WRITE, 0, NULL,
               OPEN_ALWAYS, 0, NULL ), // Host function.
   &::CloseHandle,                    // Mate function.
   (HANDLE)INVALID_HANDLE_VALUE != am::lambda::_1 );
                       // Mates only if the condition asserts true.

  // Treats am::mate<HANDLE> as if it is a HANDLE.
  if( INVALID_HANDLE_VALUE != hFile )
  {

    if( 100 > ::GetFileSize( hFile, NULL ) )
      throw "File size too small";
  }
}

There are several benefits of using am::mate over the existing implementations.

  1. More intuitive and well defined interfaces
  2. Accepts any unary custom mate function object which takes the return of the host function as its argument
  3. Unlike boost::shared_ptr which is limited only for the pointer type, am::mate works with arbitrary data type. i.e. integral type, floating point type, pointer type and even reference type
  4. Implicit conversion to the return type of the host function which casts an illusion that makes an am::mate variable seem as if it is a stored variable of the return type of the host function
  5. Easy, convenient and compiler-warning-free MATE() macro definitions to create an anonymous am::mate variable
  6. Mates function with a condition
  7. Provides a simple set of boolean lambda operations to support composing an anonymous function object for the condition
  8. am::mate<void> specialization when mating only a mate function without any host function call
  9. Well standard compliant and portable. Tested on VC6, VC71, VC80, gcc3.4.2
  10. No dependency on boost library but works with it very nicely
  11. Provide am::ptr_fun, am::bind1st and am::bind2nd helper template functions that work well for any calling convention (including __stdcall)
  12. am::lambda::bind up to 3 arguments for both free function and member function are provided

Basic usage

Mating a host function call with a mate function

Mating a host function call with a mate function is easy. It provides the return of the host function as the first argument (by calling it) and the mate function as the second argument of the constructor of an am::mate variable. Specifies the return type of the host function explicitly as the template argument of the am::mate when declaring it.

Use MATE() macro to create an anonymous am::mate variable if you are not concerned about using the am::mate variable.

C++
int prefix(int n) { return n + 100; }
void suffix(int n) { n -= 100; }

void test04()
{
  am::mate<int> nNumber( prefix(123), &suffix );
  assert( 23 == nNumber );

  int nNumber2 = nNumber; // Compile OK!

#if 0
  nNumber = nNumber2; // Compile error! mate is non-assignable.
  nNumber = 52;       // Compile error! mate is non-assignable.
#endif

 {
   MATE( prefix( 333 ), &suffix );

 } // calls suffix( 233 ) here

} // calls suffix( 23 ) here on exit.

An am::mate variable can be treated as a variable of the data type which is specified as the template parameter when the am::mate variable is declared. This is due to the implicit conversion operator to the data type. But also be aware that it is read-only access, which means it behaves like a const variable. i.e. an am::mate<int> variable can be treated as a const int variable.

Composing a mate function object

Mate function object will take the return of the host function as its only input argument (it should be a unary function object) when it is called. You can use any known techniques to compose and provide a unary custom function object when constructing am::mate variable.

C++
struct MyReleaseDC
{
  HWND hwnd_;
  MyReleaseDC(HWND hwnd) : hwnd_( hwnd ) { }
  int operator ()(HDC hdc) const { return ::ReleaseDC( hwnd_, hdc ); }

};  // struct MyReleaseDC

struct MySelectObject
{
  HDC hdc_;
  MySelectObject(HDC hdc) : hdc_( hdc ) { }
  HGDIOBJ operator ()(HGDIOBJ hgdiobj) const { return ::SelectObject
                           ( hdc_, hgdiobj ); }

};  // struct MySelectObject

void test05(HWND hwnd)
{
  am::mate<HDC> hdc( ::GetWindowDC( hwnd ), MyReleaseDC( hwnd ) );

  MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
                       MySelectObject( hdc ) );
}

C++
#define BOOST_BIND_ENABLE_STDCALL
#include <boost/bind.hpp>

void test06(HWND hwnd)
{
  am::mate<HDC> hdc( ::GetWindowDC( hwnd ), boost::bind
                       ( &::ReleaseDC, hwnd, _1 ) );

  MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
               boost::bind( &::SelectObject, hdc, _1 ) );
}

C++
void test07(HWND hwnd)
{
  am::mate<HDC> hdc( ::GetWindowDC( hwnd ),
               am::bind1st( &::ReleaseDC, hwnd ) );

  MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
               am::bind1st( &::SelectObject, hdc ) );
}

C++
#define AM_BIND_ENABLE_STDCALL
#include "lambda.hpp"

void test08(HWND hwnd)
{
  am::mate<HDC> hdc( ::GetWindowDC( hwnd ),
       am::lambda::bind( &::ReleaseDC, hwnd, am::lambda::_1 ) );

  MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
    am::lambda::bind( &::SelectObject, hdc, am::lambda::_1 ) );
}

Mating with a condition

Sometime there are cases in which the host function call might fail and returns the error code as a result. In such cases, the mate function should not be called. Checking the error of the host function call can be easily performed as am::mate variable can be treated as a const variable of the return type of the host function call. Use dismate_mate() member function to dismiss the mate function call.

C++
void test09()
{
  am::mate<HANDLE> hEvent( ::CreateEvent( NULL, FALSE, FALSE, NULL ),
                           &::CloseHandle );
  if( hEvent == NULL )
    hEvent.dismiss_mate();
}

However this approach cannot be used with the MATE() macro. Use the MATE_IF() macro instead and provide a unary predicate as the third input argument for a condition. MATE_IF() utilizes am::mate's other constructor which takes a unary predicate as its third input argument. If a condition is provided, the mate function will be called, when it goes out of scope, only if the provided unary predicate asserts true.

The unary predicate takes the return of the host function as its only input argument as well. Like mate function object, you can compose a unary predicate in any techniques you know of.

C++
struct EventCreated
{
  bool operator ()(HANDLE hevent) const { return NULL != hevent; }

};  // struct EventCreated

void test10()
{
  MATE_IF( ::CreateEvent( NULL, FALSE, FALSE, NULL ),
                   &::CloseHandle, EventCreated() );
}

There is one advantage of using am::mate with a condition than without a condition. When a mate function object is stored into the am::mate variable internally, the heap memory allocation is occurred. But if the condition does not hold, the heap memory allocation will not occur since the mate function will not be called.

C++
void test11()
{
  am::mate<HANDLE> hEvent( ::CreateEvent( NULL, FALSE, FALSE, NULL ),
    &::CloseHandle, (HANDLE)NULL != am::lambda::_1 );

}

am::mate<void> specialization

If the mate function is not concerned about the return of the host function which is given as its input argument, you can write a mate function object which takes the return of the host function, but then simply ignores it.

C++
#define RESOURCE_ACQUSITION_SUCCESS 100
#define RESOURCE_ACQUSITION_FAIL    200

int AcquireResource(HANDLE hres) { return RESOURCE_ACQUSITION_SUCCESS; }
void ReleaseResource(HANDLE hres) { }

struct IgnoreReturnAndReleaseResource
{
  HANDLE hres_;
  IgnoreReturnAndReleaseResource(HANDLE hres) : hres_( hres ) { }
  void operator ()(int) const { ReleaseResource( hres_ ); }

};  // struct IgnoreReturnAndReleaseResource

void test12(HANDLE hres)
{
  am::mate<int> nResult( AcquireResource( hres ),
    IgnoreReturnAndReleaseResource( hres ),
           RESOURCE_ACQUSITION_SUCCESS == am::lambda::_1  );

} // Calls ReleaseResource( hres ) on exit if AcquireResource( hres )
  // returns RESOURCE_ACQUSITION_SUCCESS

If you use boost::bind, boost::lambda::bind or am::lambda::bind to compose a function object in the way that all of its input arguments are bound, it will automatically create a unary function object that simply ignores the return of the host function, and when it is called, it will call the mate function only with all those bound input arguments.

C++
#define RESOURCE_ACQUSITION_SUCCESS 100
#define RESOURCE_ACQUSITION_FAIL    200

int AcquireResource(HANDLE hres) { return RESOURCE_ACQUSITION_SUCCESS; }
void ReleaseResource(HANDLE hres) { }

void test13(HANDLE hres)
{
  am::mate<int> nResult( AcquireResource( hres ),
    am::lambda::bind( &ReleaseResource, hres ),
           RESOURCE_ACQUSITION_SUCCESS == am::lambda::_1  );

}

If the host function is a function with void return or even we don't have a host function to call at all, am::mate<void> specialization and MATE_VOID() macro play a neat role in such cases as am::mate<void> class' constructors do not take the return of host function as their first argument.

C++
void AcquireResourceEx(HANDLE hres) { }
void ReleaseResourceEx(HANDLE hres) { }
void Cleanup() { }

void test14(HANDLE hres, int option)
{
  am::mate<void> cleanup_on_exit( &Cleanup );

  if( 0 == option )
  {
    AcquireResourceEx( hres );
    MATE_VOID( am::lambda::bind( &ReleaseResourceEx, hres ) );

  }
  else if( 1 == option )
  {
    AcquireResourceEx( hres );
    MATE_VOID_IF( am::lambda::bind( &ReleaseResourceEx, hres ),
                   am::condition( NULL != hres ) );

  }
  else if( 2 == option )
  {
    am::mate<void> dummy( ( AcquireResourceEx( hres ),
           am::lambda::bind( &ReleaseResourceEx, hres ) ) );

  }
  else if( 3 == option )
  {
    am::mate<void> dummy( ( AcquireResourceEx( hres ),
           am::lambda::bind( &ReleaseResourceEx, hres ) ),
      am::condition( NULL != hres ) );

  }
  else
  {
    MATE_VOID( ( AcquireResourceEx( hres ),
           am::lambda::bind( &ReleaseResourceEx, hres ) ) );

  }
}

It is possible to use comma (,) operator between the host function call and the mate function and then wrap them with the nested parenthesis to make it look and feel like the same style as non-void am::mate.

Utility classes and their helper template functions

Supporting parameter by reference: am::ref and am::cref

Specifies the reference type as the template argument of the am::mate class if you want to provide the return of the host function as a reference to the mate function.

C++
int & Increase(int & x) { return ++x; }
void Decrease(int & x) { --x; }

void test15(int & refCount)
{
  am::mate<int &> decreseOnExit( Increase( refCount ), &Decrease );

} //  Calls Decrease( refCount ) on exit.

By default, a template parameter is passed by value, not by reference. Thus if a mate function provided wants to take the return of the host function as a reference and MATE() macro definition is used, the specified mate function will be called with the reference to the "copy" of the argument, not with the reference to argument itself, when it goes out of scope since template parameter is passed by value. Use am::ref or am::cref function to specify explicitly to use am::ref_holder class that transforms a reference into a value.

C++
void test16(int & refCount)
{
  MATE( am::ref( Increase( refCount ) ), &Decrease );

}  //  Calls Decrease( refCount ) on exit.

Free function pointer: am::ptr_fun

am::ptr_fun is very similar to std::ptr_fun in that it is used to convert unary and binary function pointers, respectively, into unary and binary adaptable function objects. However they are different in that am::ptr_fun can convert the function pointers of any calling convention while std::ptr_fun can only convert __cdecl function pointers. In general, use std::ptr_fun and ignore am::ptr_fun if you are not concerned about the calling convention.

am::ptr_fun and other helper template functions of am::mate are designed to collaborate only with am::mate and its other sibling helper template functions, so do not mix use it with helper template functions from STL (such as std::ptr_fun, std::bind1st, std::bind2nd and so on.)

You can also use am::ptr_fun to resolve overloaded functions manually since the template rules are very strict for type checking and it is not supposed to resolve the resolution of the overloaded functions automatically.

C++
// Overloaded functions
void foo(char const *, int) { }
void foo(int) { }
void __stdcall foo(char const *) { }

void __stdcall bar(char const *) { }

void test17()
{
  // Provides function template parameter explicitly to resolve
  // overloaded functions.
  MATE( "Hello foo!",
       am::ptr_fun<void (__stdcall *)(char const *)>( &foo ) );
  MATE( "Hello foo!",
   am::bind2nd( am::ptr_fun<void (*)(char const *, int)>( &foo ), 123 ) );

  // am::pointer_to_function is not necessary,
  // unless otherwise resolving overloaded functions is required.
  MATE( "Hello bar!", am::ptr_fun( &bar ) );
  MATE( "Hello bar!", &bar );
}

Be aware that the template parameter is provided as a single function pointer and not as individual types from the function signature like STL does. It gives am::ptr_fun and its sibling helper template functions the ability to resolve overloaded functions better as well as to work well with any calling conventions, but it also prevents from being able to define the public interfaces such as first_argument_type, second_argument_type and result_type. Actually result_type is always fixed and defined as void internally since am::mate does not utilize the result_type under the hood.

NOTE

Do NOT use am::ptr_fun, am::bind1st, am::bind2nd to create a function object which wraps a free function with the calling convention other than __cdecl under non-MSVC environment. Calling convention is subject to be platform specific therefore it does not work well for non-MSVC environment, especially when the function pointer is passed as a template parameter. Use am::lambda::bind instead.

Bind the 1st or the 2nd argument of the binary function: am::bind1st and am::bind2nd

am::bind1st and am::bind2nd are similar to std::bind1st and std::bind2nd respectively but they resolve overloaded functions better as well as work for any calling conventions at the expense of not having the public interface first_argument_type. The template parameter is also required to be provided as a single function pointer and not as individual types from the function signature when resolving overloaded functions is needed.

C++
void foo(char const *, int) { }
void __stdcall bar(char const *, int) { }  // __stdcall calling convention.

void main()
{
  // MATE( "Hello foo!", std::bind2nd( &foo, 123 ) ); // Compiles error!
  // MATE( "Hello bar!", std::bind2nd( &bar, 123 ) ); // Compiles error!
  MATE( "Hello foo!", am::bind2nd( &foo, 123 ) ); // Compiles OK!
  MATE( "Hello bar!", am::bind2nd( &bar, 123 ) ); // Compiles OK!
}

Miscellaneous

am::condition can be used to convert a boolean expression into a null-nary or unary predicate.

C++
void test19(bool cond)
{
  assert( false == am::condition( 2 == 3 )() );
  assert( false == am::condition( 2 == 3 )( "123" ) );
               // Input argument is simply ignored.

  // Use am::lambda::condition instead when composing a function object
  // using am::lambda
  MATE_IF( ::CreateMutex(NULL, TRUE, NULL), &::ReleaseMutex,
    (HANDLE)NULL != am::lambda::_1 && am::lambda::condition( cond ) );
}

Use am::lambda to compose a function object

boost::lambda library provide a very powerful ability to create an anonymous function object on the spot. Unfortunately it does not support some outdated compilers which are still widely being used. In such environments, you can consider to use am::lambda.

am::lambda is a limited subset of boost::lambda which only supports the following operators:

  • Logical NOT : !
  • Logical AND : &&
  • Logical OR : ||
  • Equality operators : ==, !=
  • Relational operators : <, >, <=, >=
  • Address-of operator : &
  • Pointer-to-member operator : ->* (to access member data pointer, not member function pointer and read-only access)

The above lambda operators seems good enough to collaborate with am::mate and, most of all, it might work for those outdated compilers which boost::lambda does not support.

Also am::lambda::bind helper template functions are provided to support arguments binding similar to boost::lambda::bind.

C++
#include "mate.hpp"
#include "lambda.hpp"

class Graph { };

std::pair<bool, unsigned> AddVertex(Graph & g)
{
  bool is_success = false;
  unsigned vertex_descriptor = 0;

  // Add a new vertex.

  // return Pair of boolean success flag and the new vertex descriptor if it
  // successfully added a new vertex.
  return std::make_pair( is_success, vertex_descriptor );
}

void RemoveVertex(unsigned vertex_descriptor, Graph & g)
{
  // Remove the vertex of the specified descriptor.
}

void test20(Graph & g)
{
  typedef std::pair<bool, unsigned> result_pair;

  // 1. Calls the host function AddVertex()
  // 2. Mates RemoveVertex() with the second of the resultant
  //    std::pair of the host function
  //    only if the first of the resultant std::pair of the host function
  //    hold true.
  MATE_IF( AddVertex( g ),
    am::lambda::bind( &RemoveVertex,
   &am::lambda::_1->*&result_pair::second, g ),
    &am::lambda::_1->*&result_pair::first );
}

am::lambda::bind can resolve overloaded free functions (non-member functions) with __stdcall calling convention when AM_ENABLE_BIND_STDCALL is defined before including, directly or indirectly, "lambda.hpp". am::lambda::bind supports binding only up to three input arguments.

C++
#include "mate.hpp"

#define AM_BIND_ENABLE_STDCALL
#include "lambda.hpp"

typedef std::map<int, RECT> RECT_MAP; // Rect id -> Rectangle
typedef RECT_MAP::value_type MAP_VALUE;

MAP_VALUE ModifyRectangle(MAP_VALUE map_value) { return map_value; }
void UndoRectangle(int rect_id) { }

void test21(RECT_MAP const & rect_map, POINT pt)
{
  RECT_MAP::const_iterator it = rect_map.begin(), it_e = rect_map.end();
  for( ; it != it_e; ++it )
  {
    // 1. Calls the host function ModifyRectangle()
    // 2. Mates UndoRectangle() with the first of the
    //    iterating map value that
    //    host function returns, only if the rectangle is
    //    found to contain the
    //    specified point and the specified point is not the origin (0, 0).
    MATE_IF( ModifyRectangle( *it ),
      am::lambda::bind( &UndoRectangle,
               &am::lambda::_1->*&MAP_VALUE::first ),
      am::lambda::bind( &::PtInRect,
           &( &am::lambda::_1->*&MAP_VALUE::second ), pt ) &&
      am::lambda::condition( 0 != pt.x && 0 != pt.y ) );
  }
}

am::lambda is completely independent on am::mate and vice-versa, thus both can be used separately.

References

  1. Generic: Change the Way You Write Exception-Safe Code - Forever
  2. boost 2: shared_ptr wraps resource handles by peterchen
  3. Using shared_ptr to execute code on block exit

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Other
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionIt just work when I debug it Pin
suntoe14-Jul-07 5:57
suntoe14-Jul-07 5:57 
GeneralAutomated Exception Handling Pin
Ron Zigler3-Jun-07 20:43
Ron Zigler3-Jun-07 20:43 
GeneralWell done, mate! Pin
triendl.kj19-Mar-07 21:58
professionaltriendl.kj19-Mar-07 21:58 
GeneralRe: Well done, mate! Pin
JaeWook Choi17-Apr-07 21:52
JaeWook Choi17-Apr-07 21:52 
GeneralNice! Pin
Coolcute12-Dec-06 18:06
Coolcute12-Dec-06 18:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.