问题
in my code I use HANDLE
s from windows.h
. They are used like
HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
throw std::exception("openHandleToSomething error");
}
/* Use the handle in other functions which can throw as well */
if (!CloseHandle(h)) {
throw std::exception("closeHandle error");
}
As you see, you have to insert this CloseHandle
to every exception which can happen in the middle of acquiration and release. Therefore, it's likely you forget one (or there is a fancy SEH exception which you didn't know about) and voilà, you have your memory leak.
Recently, I've read about RAII which should remove the headaches from such cases and should call this CloseHandle
automatically. I've also seen that there is something like std::auto_ptr<someType>
in C++ which solves the problem for resources which were allocated with new
.
However, since I don't use new
and since HANDLE
is just typedef
ed to be a void *
, I wonder how I should use the std::auto_ptr<someType>
. Somehow, it should be possible to give it a custom deleter function (if (!CloseHandle(h)) { throw std::exception("closeHandle error"); }
). Creating a class would be another method since the destructor gets called any time an instance of it gets out of scope. However, it's just overkill to have a class for every simple thing.
How can I fix these accidential memory leaks?
Note that I would prefer solutions which are in pure C++ with no libraries and big dependencies except if they are really small and used in most of the environments anyways.
回答1:
One idea that comes to mind is to use boost::shared_ptr with a custom deleter.
回答2:
You can implement your own simple RAII idiom.
class auto_handle {
public:
auto_handle() : handle_() {}
~auto_handle() {
if (!CloseHandle(handle_)) {
// Don't throw here (1), manage the error in other way.
}
}
HANDLE& handle() { return handle_; }
private:
auto_handle(const auto_handle&);
auto_handle& operator=(const auto_handle&);
HANDLE handle_;
};
(1) You should never throw from a destructor.
auto_handle h;
if (!openHandleToSomething(arg1, arg2, &h.handle())) {
throw exception("openHandleToSomething error"); // Now it is safe
}
回答3:
1) Don't use auto_ptr<>
. Seriously. You don't want those headaches-- it's far too easy to slip up b/c it doesn't have familiar copy semantics.
2) Wrap HANDLE
with a simple object that provides an accessor that gives you the underlying handle. You'll need this to pass the HANDLE
in to API calls. (I'd consider an accessor preferable to an implicit conversion.)
3) I've never actually bothered wrapping HANDLE
, so I don't know if there are any surprising gotcha's. If there are, I can't point them out. I wouldn't expect any-- it's an opaque value. But then, who expects a surprising gotcha'? They're surprises, after all.
4) (Of course) implement the appropriate dtor.
回答4:
std::auto_ptr
is not suitable for this situation. It has its uses, but this isn't one of them. To correct (sort of) a point raised by Greg D, the problem with auto_ptr
isn't so much its lack of pointer semantics, as its rather odd ownership semantics -- when you assign one, you don't get a copy of the pointer, but instead a transfer of the pointer (i.e the assignee becomes the new sole owner of the resource, and the assigner no longer has anything).
You do want to wrap the handle in a class though. I've done this a number of times, and it works quite well. I haven't run into anything particularly surprising when doing it, though that doesn't necessarily mean a lot -- handles are used for a lot of things in Windows, and some of them might easily have some oddities.
回答5:
You just want a simple wrapper that gives you the handle when you pass it into a function:
#include <stdexcept>
class HWrapper
{
HANDLE h;
bool closed;
public:
HWrapper(A1 arg1,A2 arg2)
:closed(false)
{
if (!openHandleToSomething(arg1, arg2, &h))
{ throw std::runtime_error("openHandleToSomething error");
}
}
~HWrapper()
{
try
{
if (!closed)
{ close();
}
}
catch(...) {/*Exceptions should never leave a destructor */ }
// Though you may want to log somthing.
}
void close()
{
closed = true;
// Close can throw an exception.
if (!CloseHandle(h))
{ throw std::runtime_error("closeHandle error");
}
}
/*
* This allows you to just pass it to a function that takes an HANDLE
* See the function: functionThatUsesHandleButMayThrow();
*/
operator HANDLE()
{
return h;
}
private:
/*
* For your use case there is not need to copy.
* So explicitly disallow copying.
*
* Just pass the HWrapper object to any function that requires a handle.
* The built in cast operator will convert it back to a Handle to be used
* within these functions. While this object just retains ownership and
* responcability for deleting the object when you are finished.
*
* This allows simple backwards compatibility with existing code.
*/
HWrapper(HWrapper const& copy); // Don't implement
HWrapper& operator=(HWrapper const& copy); // Don't implement
};
void functionThatUsesHandleButMayThrow(HANDLE h)
{
}
int main()
{
try
{
HWrapper w(A1,A2);
functionThatUsesHandleButMayThrow(w);
/*
* If you do not care about close throwing an excepion.
* Then jsut let it fall out of scope. The destructor
* will try and clean up. But if it fails it will drop the
* exception.
*
* This is required because if another exception is propogating
* throwing an exception terminates the application.
*/
}
catch(std::exception const& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
try
{
HWrapper w2(A1,A2);
functionThatUsesHandleButMayThrow(w2);
/*
* If you do care abou the exception
* The call close() manually. The exception will be thrown.
*
* But if an exception is already been thrown in
* functionThatUsesHandleButMayThrow() then we will try and close it
* in the destructor and not throw another exception.
*/
w2.close();
}
catch(std::exception const& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
}
回答6:
HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
throw std::exception("openHandleToSomething error");
}
Here it is :
auto d = [](decltype(h)* a) { if(a) ::CloseHandle(*a); };
std::unique_ptr<decltype(h), decltype(d)> buf(&h, d);
来源:https://stackoverflow.com/questions/1556168/making-a-non-object-resource-raii-compliant