如果将RAII提供给拥有项的库控制会话,那么将RAII封装到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); }
};这种设计的缺点是允许错误的行为:可以在对会话对象的所有Item对象进行析构之前对其进行析构(和调用CloseSession函数)。这很烦人,因为它不应该发生。即使这种错误行为是可能的,因此使用valid无效,我还是希望通过在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留在会话类的接口中。我在RAII系列中寻找更多的东西(对我来说,这意味着使用项目的构造函数),但是无法想象有一种方法来封装它,以确保正确的销毁顺序。
此外,使用指针、新建和删除是太老的世纪C++。应该可以使用项目向量(而不是Item*),代价是正确定义类项的移动语义,但代价是允许为项创建未初始化的二等公民项对象的默认构造函数。
有更好的设计思路吗?
发布于 2010-09-16 10:59:17
通过添加另一个层(并使您的RAII更加明确),您可以获得相当整洁的东西。会话和项的默认复制构造函数和赋值执行正确的操作。会话的句柄将在关闭所有项的句柄后关闭。没有必要把孩子们的向量放在身边,共享的指针为你追踪所有这些.所以我觉得它应该做你需要的一切。
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;
};发布于 2010-09-16 11:24:37
我认为这是一个有趣的问题。
首先,对于RAII,通常需要实现复制构造函数和赋值操作符,在这里,HANDLE const会阻止它们,但是您真的想要无法复制的对象吗?最好也让他们例外安全。
此外,还存在id的问题:您必须确保唯一性,还是框架为您做了?
编辑
自我第一次答覆以来,当局已就有关规定提出了建议,即:
在这种情况下,您有两个设计方案:
Observer模式:Item被链接回与它一起创建的Session,Session在它死后通知它(使用会话管理器,这是通过让会话管理器拥有会话并让Item查询管理器关于它的会话来实现的)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;
};和会话类
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对象负责管理Item的生存期,实际的Item对象不过是句柄的代理。Session死亡时,所有到项的HANDLE实际上都会关闭。微妙的问题:这段代码在多线程应用程序中并不安全,但是我不知道是否需要完全序列化对OpenItem和CloseItem的访问,因为我不知道底层库是否是线程安全的。
请注意,在此设计中,不能复制Session对象。显然,我们可以创建一个SessionManager对象(通常是一个单例,但这不是必要的),并让他以完全相同的方式管理Session :)
发布于 2010-12-11 06:45:21
若要扩展STLSoft的评论,请使用STLSoft的手柄智能指针,如下所示:
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
https://stackoverflow.com/questions/3725826
复制相似问题