Table of Contents
- Introduction
- Basic usage
- Mating a host function call with a mate function
- Composing a mate function object
- Mating with a condition
am::mate<void>
specialization
- Utility classes and their helper template functions
- Supporting parameter by reference:
am::ref
and am::cref
- Free function pointer:
am::ptr_fun
- Bind the 1st or the 2nd argument of the binary function:
am::bind1st
and am::bind2nd
- Miscellaneous
- Use
am::lambda
to compose a function object - References
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.
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.
#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 );
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.
#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 ), &::CloseHandle, (HANDLE)INVALID_HANDLE_VALUE != am::lambda::_1 );
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.
- More intuitive and well defined interfaces
- Accepts any unary custom mate function object which takes the return of the host function as its argument
- 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 - 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 - Easy, convenient and compiler-warning-free
MATE()
macro definitions to create an anonymous am::mate
variable - Mates function with a condition
- Provides a simple set of boolean lambda operations to support composing an anonymous function object for the condition
am::mate<void>
specialization when mating only a mate function without any host function call - Well standard compliant and portable. Tested on VC6, VC71, VC80, gcc3.4.2
- No dependency on boost library but works with it very nicely
- Provide
am::ptr_fun
, am::bind1st
and am::bind2nd
helper template functions that work well for any calling convention (including __stdcall
) am::lambda::bind
up to 3 arguments for both free function and member function are provided
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.
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;
#if 0
nNumber = nNumber2; nNumber = 52; #endif
{
MATE( prefix( 333 ), &suffix );
}
}
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.
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.
struct MyReleaseDC
{
HWND hwnd_;
MyReleaseDC(HWND hwnd) : hwnd_( hwnd ) { }
int operator ()(HDC hdc) const { return ::ReleaseDC( hwnd_, hdc ); }
};
struct MySelectObject
{
HDC hdc_;
MySelectObject(HDC hdc) : hdc_( hdc ) { }
HGDIOBJ operator ()(HGDIOBJ hgdiobj) const { return ::SelectObject
( hdc_, hgdiobj ); }
};
void test05(HWND hwnd)
{
am::mate<HDC> hdc( ::GetWindowDC( hwnd ), MyReleaseDC( hwnd ) );
MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
MySelectObject( hdc ) );
}
#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 ) );
}
void test07(HWND hwnd)
{
am::mate<HDC> hdc( ::GetWindowDC( hwnd ),
am::bind1st( &::ReleaseDC, hwnd ) );
MATE( ::SelectObject( hdc, ::GetStockObject( BLACK_PEN ) ),
am::bind1st( &::SelectObject, hdc ) );
}
#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 ) );
}
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.
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.
struct EventCreated
{
bool operator ()(HANDLE hevent) const { return NULL != hevent; }
};
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.
void test11()
{
am::mate<HANDLE> hEvent( ::CreateEvent( NULL, FALSE, FALSE, NULL ),
&::CloseHandle, (HANDLE)NULL != am::lambda::_1 );
}
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.
#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_ ); }
};
void test12(HANDLE hres)
{
am::mate<int> nResult( AcquireResource( hres ),
IgnoreReturnAndReleaseResource( hres ),
RESOURCE_ACQUSITION_SUCCESS == am::lambda::_1 );
}
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.
#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.
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
.
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.
int & Increase(int & x) { return ++x; }
void Decrease(int & x) { --x; }
void test15(int & refCount)
{
am::mate<int &> decreseOnExit( Increase( refCount ), &Decrease );
}
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.
void test16(int & refCount)
{
MATE( am::ref( Increase( refCount ) ), &Decrease );
}
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.
void foo(char const *, int) { }
void foo(int) { }
void __stdcall foo(char const *) { }
void __stdcall bar(char const *) { }
void test17()
{
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 ) );
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.
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.
void foo(char const *, int) { }
void __stdcall bar(char const *, int) { }
void main()
{
MATE( "Hello foo!", am::bind2nd( &foo, 123 ) ); MATE( "Hello bar!", am::bind2nd( &bar, 123 ) ); }
am::condition
can be used to convert a boolean expression into a null
-nary or unary predicate.
void test19(bool cond)
{
assert( false == am::condition( 2 == 3 )() );
assert( false == am::condition( 2 == 3 )( "123" ) );
MATE_IF( ::CreateMutex(NULL, TRUE, NULL), &::ReleaseMutex,
(HANDLE)NULL != am::lambda::_1 && am::lambda::condition( cond ) );
}
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
.
#include "mate.hpp"
#include "lambda.hpp"
class Graph { };
std::pair<bool, unsigned> AddVertex(Graph & g)
{
bool is_success = false;
unsigned vertex_descriptor = 0;
return std::make_pair( is_success, vertex_descriptor );
}
void RemoveVertex(unsigned vertex_descriptor, Graph & g)
{
}
void test20(Graph & g)
{
typedef std::pair<bool, unsigned> result_pair;
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.
#include "mate.hpp"
#define AM_BIND_ENABLE_STDCALL
#include "lambda.hpp"
typedef std::map<int, RECT> RECT_MAP; 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 )
{
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.
- Generic: Change the Way You Write Exception-Safe Code - Forever
- boost 2: shared_ptr wraps resource handles by peterchen
- Using shared_ptr to execute code on block exit