如何将 C API 封装到 RAII C++课程?

发布于 2024-09-24 01:04:46 字数 2357 浏览 1 评论 0原文

给定一个控制拥有项目的会话的库的 C API,将 C API 封装到 RAII C++ 类中的最佳设计是什么?

C API 如下所示:

HANDLE OpenSession(STRING sessionID);
void CloseSession(HANDLE hSession);
HANDLE OpenItem(HANDLE hSession, STRING itemID);
void CloseItem(HANDLE hItem);

加上对这些类型之一(Session 或 Item)有用的其他函数,并直接映射到相关对象的 C++ 成员函数。但这里不需要它们。我的主要兴趣是这些对象的构造和销毁,使用 RAII 来管理这些类的正确打开和关闭。

我的课程设计的第一个想法是纯粹且直接的 RAII。包含的类接受容器对象作为构造函数参数。

class Session {
    HANDLE const m_hSession;
public:
    Session(STRING sessionID): m_hSession(OpenSession(sessionID)) {}
    ~Session() { CloseSession(m_hSession); }
};
class Item {
    HANDLE const m_hItem;
public:
    Item(HANDLE hSession, STRING itemID): m_hItem(OpenItem(hSession, itemID)) {}
    ~Item() { CloseItem(m_hItem); }
};

这种设计的缺点是允许不良行为:在销毁 Session 对象的所有 Item 对象之前,可以销毁 Session 对象(并调用 CloseSession 函数)。这很烦人,因为它不应该发生。即使使用 C API 时这种错误行为是可能的,因此是无效的,我也希望通过 C++ API 中的设计来避免这种情况。

这就是为什么我想知道使用以下设计,其中会话包含其项目(这显示了实际关系),并且是唯一能够构造和销毁项目的类。

class Item {
    HANDLE const m_hItem;
    Item(HANDLE hSession, STRING itemID): m_hItem(OpenItem(hSession, itemID) {}
    ~Item() { CloseItem(m_hItem); }
    friend class Session;
public:
};
class Session {
    HANDLE const m_hSession;
    typedef vector<Item *> VecItem;
    VecItem m_vecItem;
    Session(STRING sessionID): m_hSession(OpenSession(sessionID)) {}
    ~Session() {
        for (size_t n = 0 ; n < m_vecItem.size() ; ++n) delete m_vecItem[n];
        m_vecItem.clear();
        CloseSession(m_hSession);
        }
public:
    Item * OpenItem(STRING itemID) {
        Item *p = new Item(m_hSession, itemID);
        m_vecItem.push_back(p);
        return p;
        }
    void CloseItem(Item * item) {
        VecItem::iterator it = find(m_vecItem.begin(), m_vecItem.end(), item);
        if (it != m_vecItem.end()) {
            Item *p = *it; m_vecItem.erase(it); delete p;
            }
        }
};

在我看来,这是确保 Session 在其 Items 关闭之前不会关闭的唯一方法:在设计中反映 Item 对象是 Session,因此会在Session被销毁之前被销毁。

然而,它对我来说看起来有点奇怪,因为它将这些函数 OpenItem 和 CloseItem 留在了 Session 类的接口中。我一直在寻找 RAII 方面的更多内容(对我来说,这意味着使用 Item 的构造函数),但无法想象一种封装它的方法来确保正确的销毁顺序。

此外,使用指针、new 和delete 已经是老世纪的C++ 了。应该可以使用 Item 向量(而不是 Item*),但代价是正确定义 Item 类的移动语义,但代价是允许 Item 的默认构造函数创建未初始化的第二类公民项目对象。

还有更好的设计思路吗?

Given a C API to a library controlling sessions that owns items, what is the best design to encapsulate the C API into RAII C++ classes?

The C API looks like:

HANDLE OpenSession(STRING sessionID);
void CloseSession(HANDLE hSession);
HANDLE OpenItem(HANDLE hSession, STRING itemID);
void CloseItem(HANDLE hItem);

Plus other functions that are useful for one of these types (Session, or Item) and map directly to C++ member functions of the relevant object. But they are not needed here. My main interest is in the construction and destruction of these objects, using RAII to manage a correct opening and closing of these classes.

My first idea for the design of my classes, is pure and direct RAII. The contained class accepts a container object as the constructor parameter.

class Session {
    HANDLE const m_hSession;
public:
    Session(STRING sessionID): m_hSession(OpenSession(sessionID)) {}
    ~Session() { CloseSession(m_hSession); }
};
class Item {
    HANDLE const m_hItem;
public:
    Item(HANDLE hSession, STRING itemID): m_hItem(OpenItem(hSession, itemID)) {}
    ~Item() { CloseItem(m_hItem); }
};

This design have the disadvantage of allowing a bad behavior: a Session object can be destructed (and CloseSession function called) before all of its Item objects have been destructed. This is annoying, because it shouldn't happened. Even if this erroneous behavior is possible, hence not valid, using the C API, I'd like it to be avoided by design in the C++ API.

That's why I am wondering about using the following design where the Session contains its Items (this shows the actual relationship), and is the sole class able to construct and destroy Items.

class Item {
    HANDLE const m_hItem;
    Item(HANDLE hSession, STRING itemID): m_hItem(OpenItem(hSession, itemID) {}
    ~Item() { CloseItem(m_hItem); }
    friend class Session;
public:
};
class Session {
    HANDLE const m_hSession;
    typedef vector<Item *> VecItem;
    VecItem m_vecItem;
    Session(STRING sessionID): m_hSession(OpenSession(sessionID)) {}
    ~Session() {
        for (size_t n = 0 ; n < m_vecItem.size() ; ++n) delete m_vecItem[n];
        m_vecItem.clear();
        CloseSession(m_hSession);
        }
public:
    Item * OpenItem(STRING itemID) {
        Item *p = new Item(m_hSession, itemID);
        m_vecItem.push_back(p);
        return p;
        }
    void CloseItem(Item * item) {
        VecItem::iterator it = find(m_vecItem.begin(), m_vecItem.end(), item);
        if (it != m_vecItem.end()) {
            Item *p = *it; m_vecItem.erase(it); delete p;
            }
        }
};

It looks to me as the only way to ensure a Session is not closed before its Items are closed: reflecting in the design that the Item objects are members of the
Session, and therefore will be destructed before the Session is destroyed.

However, it looks a bit weird to me, as it leaves these functions OpenItem and CloseItem in the interface of the Session class. I was looking for something more in the line of RAII (for me, this means using a constructor for Item), but cannot imagine a way to encapsulate it that would ensure correct destruction order.

Furthermore, using pointers, new and delete is too much old century C++. It should be possible to use a vector of Item (instead of Item*), at the price of defining correctly the move semantics for class Item, but it would be at the price of allowing a default constructor for Item that would create uninitialized second class citizen Item objects.

Any better design ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

独孤求败 2024-10-01 01:04:46

通过添加另一层(并使 RAII 更加明确),您可以获得一些非常简洁的东西。会话和项目的默认复制构造函数和分配做了正确的事情。所有项目的 HANDLE 关闭后,会话的 HANDLE 也将关闭。没有必要保留孩子的向量,共享指针会为你跟踪所有这些......所以我认为它应该做你需要的一切。

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;
};

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;
};
十秒萌定你 2024-10-01 01:04:46

我认为这是一个有趣的问题。

首先,对于 RAII,您通常希望实现一般的复制构造函数和赋值运算符,这里 HANDLE const 会阻止它们,但是您真的想要无法复制的对象吗?最好也让它们异常安全。

另外,还有id的问题:您必须确保唯一性还是框架会为您做到这一点?

编辑

自从我第一次回答以来,要求已经明确,即:

  • 库已经实现了引用计数,无需我们自己处理

在这种情况下,您有两种设计替代方案:

  • 使用Observer 模式:Item 链接回它创建时使用的 SessionSession 在它消亡时通知它(使用会话管理器 这是通过让会话管理器拥有会话并让 Item 向管理器查询其会话来自动实现的)
  • 使用 @Michael 的方案,其中 Item 共享所有权Session 对象,以便在至少一个 Item 仍然存在的情况下,Session 不能被销毁。

我不太喜欢第二种解决方案,因为 Session 的生命周期更难跟踪:您无法可靠地终止它。

另一方面,正如您所说,第一个解决方案意味着存在空对象,这可能是不可接受的。

旧解决方案:

至于实际设计,我建议:

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;
};

Session 类

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;
};

这传达了您所要求的含义:

  • Session 对象负责管理 < code>Item,实际的 Item 对象只不过是句柄的代理。
  • 您的对象有一个确定性生命周期:只要 Session 死亡,所有项目的 HANDLE 都被有效关闭

微妙的问题:这段代码在多线程应用程序中不安全,但我不知道我们是否需要完全序列化对 < code>OpenItem 和 CloseItem 因为我不知道底层库是否是线程安全的。

请注意,在此设计中,Session 对象无法复制。显然,我们可以创建一个 SessionManager 对象(通常是单例,但这不是必需的)并让他以同样的方式管理 Session :)

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 :)

゛清羽墨安 2024-10-01 01:04:46

要扩展STLSoft的评论,请使用STLSoft的scoped_handle 智能指针,如:

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
  }
}

如果“null”句柄值不为0,则执行类似以下操作:

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

HTH

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文