How to encapsulate a C API into RAII C++ classes?

穿精又带淫゛_ 提交于 2019-12-03 08:24:29
Michael Anderson

By adding another layer (and making your RAII a little more explicit) you can get something pretty neat. Default copy constructors and assignment for Sessions and Items do the right thing. The HANDLE for the session will be closed after the HANDLE for all the items is closed. There's no need to keep vectors of children around, the shared pointers track all that for you ... So I think it should do everything you need.

class SessionHandle
{
   explicit SessionHandle( HANDLE in_h ) : h(in_h) {}
   HANDLE h;
   ~SessionHandle() { if(h) CloseSession(h); }
};

class ItemHandle
{
   explicit ItemHandle( HANDLE in_h ) : h(in_h) {}
   HANDLE h;
   ~ItemHandle() { if(h) CloseItem(h); }
};

class Session
{
   explicit Session( STRING sessionID ) : session_handle( OpenSession(sessionID) )
   {
   }
   shared_ptr<SessionHandle> session_handle;
};

class Item
{
   Item( Session & s, STRING itemID ) : 
     item_handle( OpenItem(s.session_handle.get(), itemID ) ), 
     session_handle( s.session_handle )
   {
   }
   shared_ptr<ItemHandle> item_handle;
   shared_ptr<SessionHandle> session_handle;
};

This is an interesting problem I think.

First of all, for RAII, you usually want to implement the Copy Constructor and the Assignment Operator in general, here the HANDLE const would prevent them, but do you really want objects that can't be copied ? And better make them exception safe too.

Also, there is the issue of id: do you have to ensure uniqueness or does the framework do it for you ?

EDIT:

The requirements have been precised since my first answer, namely:

  • the library implements reference counting already, no need to handle it ourselves

In this case, you have two design alternatives:

  • Use the Observer Pattern: the Item is linked back to the Session it was created with, the Session notifies it when it dies (using a session manager this is automated by having the session manager owning the session and having the Item query the manager about its session)
  • Use @Michael's scheme in which the Items share the ownership of the Session object, so that the Session cannot be destroyed while at least one Item still exist.

I don't like the second solution much because the lifetime of the Session is much harder to track then: you can't reliably kill it.

On the other hand, as you said, the first solution implies the existence of null objects, which may not be acceptable.

Old Solution:

As for the actual design, I would propose:

class Item
{
public:
  Item(): mHandle() {}

  Item(Session& session, std::string id): mHandle(session.CreateItem(id))
  {
  }

  void swap(Item& rhs)
  {
    using std::swap;
    swap(mHandle, rhs.mHandle);
  }

  void reset()
  {
    mHandle.reset();
  }

  /// Defensive Programming
  void do()
  {
    assert(mHandle.exists() && "do - no item");
    // do
  }

private:
  boost::weak_ptr<HANDLE const> mHandle;
};

And the Session class

class Session
{
public:

private:
  typedef boost::weak_ptr<HANDLE const> weak_ptr;
  typedef boost::shared_ptr<HANDLE const> shared_ptr;
  typedef boost::unordered_map<std::string, shared_ptr> map_type;

  friend class Item;
  struct ItemDeleter
  {
    void operator()(HANDLE const* p) { CloseItem(*p); }
  };

  weak_ptr CreateItem(std::string const& id)
  {
    map_type::iterator it = mItems.find(id);
    if (it != mItems.end()) return it->second;

    shared_ptr p = shared_ptr(new OpenItem(mHandle, id), ItemDeleter());
    std::pair<map_type::iterator, bool> result =
      mItems(std::make_pair(id, p));

    return result.first->second;
  }

  map_type mItems;
  HANDLE const mHandle;
};

This conveys the meaning you asked for:

  • The Session object is responsible for managing the lifetime of Items, the actual Item object being no more than a proxy to the handle
  • You have a deterministic lifetime of your objects: whenever the Session dies, all the HANDLE to items are effectively closed

Subtle issues: this code is not safe in a multithreading application, but then I have no idea whether we need to fully serialize accesses to OpenItem and CloseItem as I don't know if the underlying library is thread-safe.

Note that in this design, the Session object cannot be copied. Obviously we could create a SessionManager object (typically a singleton, but it's not necessary) and have him manage the Sessions in the very same way :)

To expand on STLSoft's comment, use STLSoft's scoped_handle smart pointer, as in:

HANDLE hSession = OpenSession("session-X");
if(!hSession) {
 // Handle failure to open session
}
else {
  stlsoft::scoped_handle<HANDLE> session_release(hSession, CloseSession);

  HANDLE hItem = OpenItem(hSession, "item-Y");
  if(!hItem) {
     // Handle failure to open item
  }
  else {
      stlsoft::scoped_handle<HANDLE> item_release(hItem, CloseItem);

    // Use item
  }
}

If the "null" handle value is not 0, then do something like:

if(hSession != -1) {
 // Handle failure to open session
}
else {
  stlsoft::scoped_handle<HANDLE> session_release(hSession, CloseSession, -1);

HTH

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!