如何将 C API 封装到 RAII C++ 类中? [英] How to encapsulate a C API into RAII C++ classes?

查看:30
本文介绍了如何将 C API 封装到 RAII C++ 类中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为控制拥有项目的会话的库提供 C API,将 C API 封装到 RAII C++ 类的最佳设计是什么?

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?

C API 看起来像:

The C API looks like:

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

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

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.

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

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

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

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.

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

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

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

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.

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

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.

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

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.

有更好的设计理念吗?

推荐答案

通过添加另一个层(并使您的 RAII 更加明确),您可以获得非常整洁的东西.Sessions 和 Items 的默认复制构造函数和赋值做正确的事情.会话的 HANDLE 将在所有项目的 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;
};

这篇关于如何将 C API 封装到 RAII C++ 类中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆