views:

78

answers:

3

Hi,

I have some original code that manages exception safety like this:

void foo() {
    HDC hdc = //get an HDC
    HBITMAP hbitmap = //get an HBITMAP

    HGDIOBJ hbitmapOld = SelectObject(hdc, hbitmap);

    try {
        //do something that may throw an exception
    } catch (...) {
        SelectObject(hdc, hbitmapOld);
        throw;
    }
}

Now I want to get rid of the try block and use unique_ptr to select the old bitmap automatically. So I wrote something like this:

void foo() {
    //...

    //HGDIOBJ is defined as void*
    std::unique_ptr<void, std::function<HGDIOBJ(HGDIOBJ)>>
        hbitmapOld(SelectObject(hdc, hbitmap), std::bind(SelectObject, hdc, _1));
}

But it doesn't compile. How to make it right?

Error message:

1>c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxcallfun(7): error C2664: 'HGDIOBJ (HDC,HGDIOBJ)' : cannot convert parameter 2 from 'boost::arg<I>' to 'HGDIOBJ'
1>          with
1>          [
1>              I=1
1>          ]
1>          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxbind1(292) : see reference to function template instantiation '_Ret std::tr1::_Callable_fun<_Ty,_Indirect>::_ApplyX<_Ret,_Arg&,boost::arg<I>&>(_Arg0,_Arg1) const' being compiled
1>          with
1>          [
1>              _Ret=_Rx,
1>              _Ty=HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),
1>              _Indirect=false,
1>              _Arg=HDC,
1>              I=1,
1>              _Arg0=HDC &,
1>              _Arg1=boost::arg<1> &
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxbind0(31) : see reference to function template instantiation '_Ret std::tr1::_Bind2<_Callable,_Arg0,_Arg1>::_ApplyX<_Rx,void&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&,std::tr1::_Nil&>(_Barg0,_Barg1,_Barg2,_Barg3,_Barg4,_Barg5,_Barg6,_Barg7,_Barg8,_Barg9)' being compiled
1>          with
1>          [
1>              _Ret=_Rx,
1>              _Callable=std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,
1>              _Arg0=HDC,
1>              _Arg1=boost::arg<1>,
1>              _Barg0=HGDIOBJ &,
1>              _Barg1=std::tr1::_Nil &,
1>              _Barg2=std::tr1::_Nil &,
1>              _Barg3=std::tr1::_Nil &,
1>              _Barg4=std::tr1::_Nil &,
1>              _Barg5=std::tr1::_Nil &,
1>              _Barg6=std::tr1::_Nil &,
1>              _Barg7=std::tr1::_Nil &,
1>              _Barg8=std::tr1::_Nil &,
1>              _Barg9=std::tr1::_Nil &
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxcallobj(13) : see reference to function template instantiation 'void *std::tr1::_Bind_base<_Ret,_BindN>::operator ()<_Arg0&>(_Carg0)' being compiled
1>          with
1>          [
1>              _Ret=HGDIOBJ ,
1>              _BindN=std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>,
1>              _Arg0=HGDIOBJ,
1>              _Carg0=HGDIOBJ &
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxfunction(65) : see reference to function template instantiation '_Ret std::tr1::_Callable_obj<_Ty>::_ApplyX<_Rx,_Arg0&>(void)' being compiled
1>          with
1>          [
1>              _Ret=HGDIOBJ,
1>              _Ty=std::tr1::_Bind<HGDIOBJ,HGDIOBJ,std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>>,
1>              _Rx=HGDIOBJ,
1>              _Arg0=HGDIOBJ
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxfunction(64) : while compiling class template member function 'HGDIOBJ std::tr1::_Impl_no_alloc1<_Callable,_Rx,_Arg0>::_Do_call(_Arg0)'
1>          with
1>          [
1>              _Callable=_MyWrapper,
1>              _Rx=HGDIOBJ ,
1>              _Arg0=HGDIOBJ 
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxfunction(386) : see reference to class template instantiation 'std::tr1::_Impl_no_alloc1<_Callable,_Rx,_Arg0>' being compiled
1>          with
1>          [
1>              _Callable=_MyWrapper,
1>              _Rx=HGDIOBJ ,
1>              _Arg0=HGDIOBJ 
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxfunction(369) : see reference to function template instantiation 'void std::tr1::_Function_impl1<_Ret,_Arg0>::_Reset0o<_Myimpl,_Fty,std::allocator<_Ty>>(_Fty,_Alloc)' being compiled
1>          with
1>          [
1>              _Ret=HGDIOBJ ,
1>              _Arg0=HGDIOBJ ,
1>              _Fty=std::tr1::_Bind<HGDIOBJ,HGDIOBJ,std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>>,
1>              _Ty=std::tr1::_Function_impl1<HGDIOBJ ,HGDIOBJ >,
1>              _Alloc=std::allocator<std::tr1::_Function_impl1<HGDIOBJ ,HGDIOBJ >>
1>          ]
1>          c:\program files (x86)\microsoft visual studio 10.0\vc\include\functional(113) : see reference to function template instantiation 'void std::tr1::_Function_impl1<_Ret,_Arg0>::_Reset<_Fx>(_Fty)' being compiled
1>          with
1>          [
1>              _Ret=HGDIOBJ ,
1>              _Arg0=HGDIOBJ ,
1>              _Fx=std::tr1::_Bind<HGDIOBJ,HGDIOBJ,std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>>,
1>              _Fty=std::tr1::_Bind<HGDIOBJ,HGDIOBJ,std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>>
1>          ]
1>          r:\programming\windows\biota\library\orchid\src\pixmap.cpp(182) : see reference to function template instantiation 'std::tr1::function<_Fty>::function<std::tr1::_Bind<_Result_type,_Ret,_BindN>>(_Fx)' being compiled
1>          with
1>          [
1>              _Fty=HGDIOBJ (HGDIOBJ),
1>              _Result_type=HGDIOBJ,
1>              _Ret=HGDIOBJ,
1>              _BindN=std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>,
1>              _Fx=std::tr1::_Bind<HGDIOBJ,HGDIOBJ,std::tr1::_Bind2<std::tr1::_Callable_fun<HGDIOBJ (__stdcall *const )(HDC,HGDIOBJ),false>,HDC,boost::arg<1>>>
1>          ]
1>c:\program files (x86)\microsoft visual studio 10.0\vc\include\xxcallfun(7): error C2664: 'HGDIOBJ (HDC,HGDIOBJ)' : cannot convert parameter 1 from 'boost::arg<I>' to 'HDC'
1>          with
1>          [
1>              I=1
1>          ]
1>          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>
1>Build FAILED.
+2  A: 

I think a better solution is to write a small class that will do the work in the constructor, and do the roll-back in the destructor. Destructors are always called for stack classes when the stack unwinds during exceptions. I think even if you got your unique_ptr code working, it's a much more awkward solution than that.

For example, in some of my code, I have a scoped_noredraw class that prevents a window refreshing while it is updated. If the function returns either normally or by exception, the window refreshing is always turned back on in the destructor.

AshleysBrain
+1 Wow, you're totally right. I'm actually embarrassed I didn't think of that while writing the above comments.
Chris Jester-Young
+1  A: 

What you want here is the Scope Guard idiom: http://www.drdobbs.com/cpp/184403758

Nemanja Trifunovic
A: 

Thank you, AshleysBrain and Nemanja Trifunovic, for suggesting a scope guard approach.

The way I see it is that unique_ptr is a more general implementation of scope guard (it should be able to do what scope guard does, and something more), and the logic of my method is correct, so in theory it should work.

After some testing I finally get why it doesn't work. It's because SelectObject() uses the __stdcall convention, and Microsoft chooses to ignore this inconvenient behavior of itself when writing std::bind. :(

Precisely what more can unique_ptr do than a scope guard? I can't see that it can do any more, especially since you can run arbitrary code in constructors and destructors, and the code for a scope guard is much easier to understand.
AshleysBrain
As its name implies (quite explicitly, lol), unqiue_ptr can be (and is) used as a pointer.
So? What does that actually enable you to *do*? You can still take the address of a stack variable and pretend it's a pointer if you want...
AshleysBrain