Учитывая C API для библиотеки, управляющей сеансами, которая владеет элементами, каков наилучший дизайн для инкапсуляции C API в классы RAII C ++?
API C выглядит следующим образом:
HANDLE OpenSession(STRING sessionID);
void CloseSession(HANDLE hSession);
HANDLE OpenItem(HANDLE hSession, STRING itemID);
void CloseItem(HANDLE hItem);
Плюс другие функции, которые полезны для одного из этих типов (Session или Item) и отображаются непосредственно в функции-члены C ++ соответствующего объекта. Но они здесь не нужны. Мой основной интерес заключается в создании и уничтожении этих объектов с использованием RAII для управления правильным открытием и закрытием этих классов.
Моя первая идея для дизайна моих классов - pure и прямой 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 может быть разрушен (и вызвана функция CloseSession) до того, как все его объекты Item будут уничтожены. Это раздражает, потому что этого не должно было случиться. Даже если это ошибочное поведение возможно, а следовательно, и недопустимо при использовании 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;
}
}
};
Мне кажется, что это единственный способ убедиться, что сессия не закрыта до закрытия ее элементов: это отражает в дизайне, что объекты Item являются членами
Сессия, и, следовательно, будет разрушена до того, как Сессия будет уничтожена.
Однако, это выглядит немного странно для меня, поскольку оставляет эти функции OpenItem и CloseItem в интерфейсе класса Session. Я искал что-то большее в строке RAII (для меня это означает использование конструктора для Item), но не могу представить способ инкапсулировать его, который бы обеспечил правильный порядок уничтожения.
Более того, использование указателей new и delete - это слишком много C ++ старого века. Должна быть возможность использовать вектор Item (вместо Item *) ценой правильного определения семантики перемещения для класса Item, но это будет ценой разрешения конструктора по умолчанию для Item, который будет создавать неинициализированный второй класс. Гражданин Предмет предметов.
Есть лучшие идеи дизайна?