views:

154

answers:

5

I'm creating a wrapper for a HANDLE that does not work with DuplicateHandle, so instead I am trying to wrap the handle in a shared_ptr.

Imagine the following code:

class CWrapper
{
public:
    CWrapper() :
        m_pHandle(new HANDLE, &CWrapper::Close)
    {
        //code to open handle
    }

private:
    void Close() 
    { 
        //code to close handle
    }

    std::shared_ptr<HANDLE> m_pHandle;
}

I have also tried creating close with a HANDLE parameter (not ideal). Either way, I get the compiler error "Term does not evaluate to a function taking 0 arguments". Is this because of the implicit this pointer? How do I fix this? How do I call a member function from the shared pointer?

+4  A: 

You can't call a member function but you can use a custom deleter that is a global function like so:

void my_deleter(Foo *ptr)
{
 delete ptr;
 std::cout<< "using custom deleter" <<std::endl;
}
shared_ptr<Foo> pf (new Foo, my_deleter); 
shoosh
A: 

Look for deleter in boost::shared_ptr docs. I couldn't find a direct link to it, but basically it is a functor that is called when the ref is 0.

Gianni
+3  A: 

I haven't tested it, but based on the idea presented by shoosh, you may be able to pass a member function like this:

void Class::myDeleteMember(Foo *ptr)
{
 delete ptr;
 std::cout<< "using custom deleter" <<std::endl;
}
shared_ptr<Foo> pf (new Foo, boost::bind(&Class::myDeleteMember, _1)); 
inflagranti
it seems he's using tr1 since he has `std::shared_ptr` and not `boost::shared_ptr`. is `bind()` in tr1?
shoosh
I'm not to familiar with tr1, but from what I gathered according to this http://en.wikipedia.org/wiki/C%2B%2B_Technical_Report_1#Function_object_binders it is, isn't it?
inflagranti
I also toyed with the C++0x way: shared_ptr<HANDLE> ph (new HANDLE, [this](HANDLE *){ Close(); } );
DanDan
DeadMG
It is also not immune to the hidden bug described by Johannes.
DanDan
+3  A: 

I think you have your abstractions the wrong way around.

shared_ptr gives you a copyable "handle" to a shared resource that can't itself be copied. Using shared_ptr with a type that doesn't perform its own cleanup when it is deleted isn't an optimal use.

If make your class' single responsibility to clean up this inherently non-copyable resource properly in its destructor, then you can use shared_ptr to provide shared ownership which is what its single responsibility should be. (I consider HANDLE to be non-copyable as if you try to make a simple copy of a HANDLE the copies cannot be treated as independent; the last copy must be correctly closed so the owners of copies would need to know about other copies in existence.)

class CWrapper
{
public:
    CWrapper()
    {
        // code to open handle
    }

    ~CWrapper()
    {
        // code to close handle
    }

private:
    // prevent copying
    CWrapper(const CWrapper&);
    CWrapper& operator=(const CWrapper&);

    HANDLE mHandle;
};

Now use shared_ptr<CWrapper> where you need to shared the handle, you can use a typedef if you think that this is too verbose.

A custom deleter is an overly complex solution, IMHO.

Charles Bailey
I think you are right, my abstractions are wrong. I always thought it was unnatural to prevent the copying of objects but now with your shared_ptr<CWrapper> technique I realise I only need to prevent the copying of this class! Thank you.
DanDan
+2  A: 

If you need to access non-static members from within Close you need to bind its this argument properly

CWrapper() :
   m_pHandle(new HANDLE, boost::bind(&CWrapper::Close, this, _1)) {
    //code to open handle
}

This, however contains a hidden bug. Your object is copyable, and you bind the deleter to the object of *this. The handle is associated with the first wrapper you create, but if you copy the wrapper, the handle is shared but associated with the first wrapper, which may not exist anymore:

CWrapper getWrapper() { CWrapper w; return w;  }
CWrapper x = getWrapper();

After that code was executed and x is going to be destroyed, behavior is undefined because x's destruction of the internal handle pointer will try to use the object bound in w's constructor invokation - however that object doesn't exist anymore!

A solution to this can be to store the data associated with the handle in the allocated object itself, instead of trying to store it in the toplevel handle object, like in the following code

class CWrapper
{
public:
  CWrapper():m_pHandle(new CHandle)
  { }

private:
    // This class cannot be copied
    class CHandle : boost::noncopyable {
      friend class CWrapper;

      CHandle() 
        :m_pHandle(new HANDLE) {
          // code to open handle
      }

      ~CHandle() {
        // code to close this handle, making use of 
        // auxilary data for whatever reason
      }

    private:
      boost::scoped_ptr<HANDLE> m_pHandle;
      // auxilary data associated with the handle...
    };

    boost::shared_ptr<CHandle> m_pHandle;
};

The auxilary data is not not stored in the handle anymore, but together with the data that's shared among all copy of the wrapper. The shared data itself is created and destroyed using its normal constructor and destructor.

CWrapper getHandle() { return myHandle; }
CWrapper w = getHandle();

The last wrapper going out of life will destroy the handle, which is explicitly shared among all wrappers.

Johannes Schaub - litb
+1 for hidden bug.
Konrad Rudolph
This is a really beautiful solution and a very interesting way of thinking about resource management. Thank you for your answer! And thanks for the tip about the this bug. I almost used a lamda solution and used this without thinking.
DanDan