Учитывая 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 может быть уничтожен (и вызвана функция CloseSession) до того, как будут уничтожены все его объекты Item. Это раздражает, потому что этого не должно было случиться. Даже если такое ошибочное поведение возможно и, следовательно, недействительно при использовании 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;
}
}
};
Мне кажется, что это единственный способ гарантировать, что сеанс не будет закрыт до закрытия его элементов: отражение в дизайне того, что объекты элементов являются членами сеанса и, следовательно, будут уничтожены до того, как будет уничтожен сеанс.
Однако мне это кажется немного странным, так как оставляет эти функции OpenItem и CloseItem в интерфейсе класса Session. Я искал что-то большее в линейке RAII (для меня это означает использование конструктора для Item), но не могу представить способ его инкапсуляции, обеспечивающий правильный порядок уничтожения.
Кроме того, использование указателей, new и delete — это слишком старый век С++. Должна быть возможность использовать вектор Item (вместо Item*) ценой правильного определения семантики перемещения для класса Item, но это будет ценой разрешения конструктора по умолчанию для Item, который создаст неинициализированный второй класс. Объекты Citizen Item.
Есть идеи получше?
OpenItem, получу ли я тот жеHANDLE? Если это так, то нам нужно управлять вызовомCloseItemс подсчетом ссылок, чтобы сделать это правильно, что, я думаю, налагает сортировкуFactoryкак дляItems, так и дляSessions. - person Matthieu M.   schedule 16.09.2010OpenItem, вы получите один и тот же дескриптор. Это не то, чего мне нужно избегать, поскольку библиотека имеет встроенный подсчет ссылок. Это означает, что при повторном открытии Item будет фактически закрыт только после второго вызоваCloseItem. Это функция, с которой я могу жить, и я даже считаю это вольностью для пользователя библиотеки, которую я не хочу удалять. - person Didier Trosset   schedule 16.09.2010