代码之家  ›  专栏  ›  技术社区  ›  Didier Trosset

如何将C API封装到RAII C++类中?

  •  7
  • Didier Trosset  · 技术社区  · 14 年前

    C API看起来像:

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

    我设计课程的第一个想法是 纯净的 和直接的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); }
    };
    

    这种设计的缺点是允许出现错误的行为:在销毁会话对象的所有项对象之前,可以先销毁会话对象(并调用CloseSession函数)。这很烦人,因为这不应该发生。即使这种错误行为是可能的,因此无效,使用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;
                }
            }
    };
    

    在我看来,这是确保会话在其项关闭之前不关闭的唯一方法:在设计中反映出项对象是会话的成员

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

    4 回复  |  直到 14 年前
        1
  •  5
  •   rems4e    9 年前

    通过添加另一层(并使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;
    };
    
        2
  •  2
  •   Matthieu M.    14 年前

    我认为这是一个有趣的问题。

    首先,对于RAII,通常需要实现复制构造函数和赋值操作符,这里 HANDLE const 会阻止它们,但您真的想要无法复制的对象吗?最好让他们也安全些。

    还有一个问题是 id :您必须确保唯一性还是框架为您做到了这一点?

    编辑

    从我第一次回答起,这些要求就被精确化了,即:

    • 库已经实现了引用计数,不需要自己处理

    在本例中,有两种设计方案:

    • Observer 图案:the Item 链接回 Session 它是用 项目 向管理器查询其会话)
    • 使用@Michael的方案 项目 会话 对象,以便 当至少有一个 项目 仍然存在。

    我不太喜欢第二种解决方案,因为 会话

    另一方面,如您所说,第一个解决方案意味着空对象的存在,这可能是不可接受的。

    至于实际设计,我建议:

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

    这传达了您要求的含义:

    • 会话 对象负责管理 项目 项目 对象只不过是句柄的代理
    • 你的物体:每当 会话 HANDLE 到项有效关闭

    微妙的问题:这段代码在多线程应用程序中是不安全的,但是我不知道我们是否需要完全序列化对的访问 OpenItem CloseItem

    注意,在这个设计中 会话 无法复制对象。显然我们可以创造一个 SessionManager 对象(通常是一个单例,但这不是必需的)并让他管理 会话 同样的道理:)

        3
  •  1
  •   dcw    14 年前

    展开 的注释,使用STLSoft的 scoped_handle

    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公司