7

アイテムを所有するセッションを制御するライブラリへのCAPIを考えると、CAPIをRAIIC ++クラスにカプセル化するための最良の設計は何ですか?

CAPIは次のようになります。

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

この設計には、悪い動作を許可するという欠点があります。すべてのItemオブジェクトが破棄される前に、Sessionオブジェクトが破棄される(および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;
            }
        }
};

アイテムが閉じられる前にセッションが閉じられないようにする唯一の方法は、アイテムオブジェクトがセッションのメンバーであるため、セッションが破棄される前に破棄されることを設計に反映しているように見えます。

ただし、Sessionクラスのインターフェイスにこれらの関数OpenItemとCloseItemが残っているため、私には少し奇妙に見えます。私はRAIIのラインでもっと何かを探していました(私にとって、これはItemのコンストラクターを使用することを意味します)が、正しい破棄順序を保証するカプセル化の方法を想像することはできません。

さらに、ポインタを使用すると、newとdeleteは古すぎるC++です。クラスItemの移動セマンティクスを正しく定義するという代償を払って(Item *の代わりに)Itemのベクトルを使用できるはずですが、初期化されていない2番目のクラスを作成するItemのデフォルトコンストラクターを許可するという代償があります。市民アイテムオブジェクト。

より良いデザインのアイデアはありますか?

4

4 に答える 4

5

別のレイヤーを追加する(そしてRAIIをもう少し明確にする)ことで、かなりきれいなものを得ることができます。デフォルトのコピーコンストラクターとセッションおよびアイテムの割り当ては正しいことを行います。セッションのHANDLEは、すべてのアイテムのHANDLEが閉じられた後に閉じられます。子のベクトルを保持する必要はありません。共有ポインタがすべてを追跡します...したがって、必要なすべてを実行する必要があると思います。

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-16T10:59:17.940 に答える
2

これは興味深い問題だと思います。

まず、RAIIの場合、通常、コピーコンストラクターと代入演算子を一般的に実装する必要があります。ここでHANDLE constはそれらを防ぐことができますが、コピーできないオブジェクトが本当に必要ですか?そして、それらを例外安全にする方がよいでしょう。

また、次の問題がありidます:一意性を確保する必要がありますか、それともフレームワークがそれを行いますか?

編集

私の最初の答え以来、要件は厳密になっています。

  • ライブラリはすでに参照カウントを実装しているので、自分で処理する必要はありません

この場合、2つの設計上の選択肢があります。

  • Observerパターンを使用する:は作成Item元にリンクされ、終了時に通知します(セッションマネージャーを使用すると、セッションマネージャーがセッションを所有し、マネージャーにセッションについてクエリを実行することで自動化されます)SessionSessionItem
  • がオブジェクトItemの所有権を共有する@Michaelのスキームを使用して、少なくとも1つがまだ存在している間はを破棄できないようにします。SessionSessionItem

の寿命Sessionを追跡するのがはるかに難しいので、私は2番目の解決策があまり好きではありません:あなたはそれを確実に殺すことはできません。

一方、あなたが言ったように、最初の解決策はnullオブジェクトの存在を意味しますが、これは受け入れられない可能性があります。

古い解決策:

実際のデザインに関しては、私は提案します:

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オブジェクトはItemsの存続期間を管理する責任があり、実際のオブジェクトItemはハンドルのプロキシにすぎません。
  • あなたはあなたのオブジェクトの決定論的な寿命Sessionを持っています:死ぬときはいつでも、すべてのHANDLEアイテムは効果的に閉じられます

微妙な問題:このコードはマルチスレッドアプリケーションでは安全ではありませんが、基になるライブラリがスレッドセーフであるかどうかわからないためOpenItem、アクセスを完全にシリアル化する必要があるかどうかわかりません。CloseItem

このデザインでは、Sessionオブジェクトをコピーできないことに注意してください。明らかに、SessionManagerオブジェクト(通常はシングルトンですが、必須ではありません)を作成Sessionし、まったく同じ方法でsを管理させることができます:)

于 2010-09-16T11:24:37.573 に答える
1

STLSoftのコメントを拡張するには、次のように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

于 2010-12-11T06:45:21.643 に答える
0

shared_ptrを使用します:http ://www.boost.org/doc/libs/1_44_0/libs/smart_ptr/sp_techniques.html#handle

于 2010-09-16T11:15:56.760 に答える