给定一个用于控制拥有项目的库的 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);
加上对这些类型(会话或项目)之一有用并直接映射到相关对象的 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 对象被破坏之前被破坏(并调用 CloseSession 函数)。这很烦人,因为它不应该发生。即使这种错误行为是可能的,因此是无效的,使用 C API,我希望在 C++ API 中通过设计来避免它。
这就是为什么我想知道使用以下设计,其中 Session 包含其项目(这显示了实际关系),并且是唯一能够构造和销毁项目的类。
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 在其 Item 关闭之前不关闭的唯一方法:在设计中反映 Item 对象是 Session 的成员,因此将在 Session 被销毁之前被销毁。
但是,它看起来有点奇怪,因为它将这些函数 OpenItem 和 CloseItem 留在了 Session 类的接口中。我一直在寻找更多 RAII 行中的东西(对我来说,这意味着使用 Item 的构造函数),但无法想象一种封装它的方法,以确保正确的销毁顺序。
此外,使用指针、new 和 delete 太过古老的世纪 C++。应该可以使用 Item 的向量(而不是 Item*),代价是正确定义 Item 类的移动语义,但代价是允许 Item 的默认构造函数创建未初始化的第二类公民 Item 对象。
有更好的设计理念吗?