web-dev-qa-db-ja.com

削除時にプールに返されるスマートポインターとしてアイテムを提供するC ++オブジェクトプール

私はc ++のアイデアを楽しんでいますが、この問題で少し立ち往生しています。

リソースのプールを管理するLIFOクラスが欲しいのですが。リソースが(acquire()を介して)要求されると、オブジェクトは_unique_ptr_として返され、削除されると、リソースがプールに返されます。

単体テストは次のようになります。

_// Create the pool, that holds (for simplicity, int objects)
SharedPool<int> pool;
TS_ASSERT(pool.empty());

// Add an object to the pool, which is now, no longer empty
pool.add(std::unique_ptr<int>(new int(42)));
TS_ASSERT(!pool.empty());

// Pop this object within its own scope, causing the pool to be empty
{
  auto v = pool.acquire();
  TS_ASSERT_EQUALS(*v, 42);
  TS_ASSERT(pool.empty());
}

// Object should now have returned to the pool
TS_ASSERT(!pool.empty())
_

重要な最終テストを除いて、テストに合格する基本的な実装:

_template <class T>
class SharedPool
{
 public:
  SharedPool(){}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.Push(std::move(t));
  }

  std::unique_ptr<T> acquire() {
    assert(!pool_.empty());
    std::unique_ptr<T> tmp(std::move(pool_.top()));
    pool_.pop();
    return std::move(tmp);
  }

  bool empty() const {
    return pool_.empty();
  }

 private:
  std::stack<std::unique_ptr<T> > pool_;
};
_

質問:acquire()がタイプの_unique_ptr_を返し、削除者がthisを認識し、this->add(...)、リソースをプールに戻します。

19
swalog

ナイーブな実装

実装では、オブジェクトをプールに返すカスタム削除機能で_unique_ptr_を使用します。 acquirereleaseはどちらもO(1)です。さらに、カスタム削除機能を備えた_unique_ptr_は、暗黙的に_shared_ptr_に変換できます。

_template <class T>
class SharedPool
{
 public:
  using ptr_type = std::unique_ptr<T, std::function<void(T*)> >;

  SharedPool() {}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.Push(std::move(t));
  }

  ptr_type acquire() {
    assert(!pool_.empty());
    ptr_type tmp(pool_.top().release(),
                 [this](T* ptr) {
                   this->add(std::unique_ptr<T>(ptr));
                 });
    pool_.pop();
    return std::move(tmp);
  }

  bool empty() const {
    return pool_.empty();
  }

  size_t size() const {
    return pool_.size();
  }

 private:
  std::stack<std::unique_ptr<T> > pool_;
};
_

使用例:

_SharedPool<int> pool;
pool.add(std::unique_ptr<int>(new int(42)));
pool.add(std::unique_ptr<int>(new int(84)));
pool.add(std::unique_ptr<int>(new int(1024)));
pool.add(std::unique_ptr<int>(new int(1337)));

// Three ways to express the unique_ptr object
auto v1 = pool.acquire();
SharedPool<int>::ptr_type v2 = pool.acquire();    
std::unique_ptr<int, std::function<void(int*)> > v3 = pool.acquire();

// Implicitly converted shared_ptr with correct deleter
std::shared_ptr<int> v4 = pool.acquire();

// Note that adding an acquired object is (correctly) disallowed:
// pool.add(v1);  // compiler error
_

この実装で深刻な問題が発生した可能性があります。次の使用法は考えられないことではありません。

_  std::unique_ptr< SharedPool<Widget> > pool( new SharedPool<Widget> );
  pool->add(std::unique_ptr<Widget>(new Widget(42)));
  pool->add(std::unique_ptr<Widget>(new Widget(84)));

  // [Widget,42] acquired(), and released from pool
  auto v1 = pool->acquire();

  // [Widget,84] is destroyed properly, together with pool
  pool.reset(nullptr);

  // [Widget,42] is not destroyed, pool no longer exists.
  v1.reset(nullptr);
  // Memory leak
_

削除者が区別するために必要な情報を生かしておく方法が必要です

  1. オブジェクトをプールに戻す必要がありますか?
  2. 実際のオブジェクトを削除する必要がありますか?

これを行う1つの方法(T.C.が提案)は、各削除者に_weak_ptr_から_shared_ptr_のメンバーをSharedPoolに保持させることです。これにより、削除者はプールが破棄されたかどうかを知ることができます。

正しい実装:

_template <class T>
class SharedPool
{
 private:
  struct External_Deleter {
    explicit External_Deleter(std::weak_ptr<SharedPool<T>* > pool)
        : pool_(pool) {}

    void operator()(T* ptr) {
      if (auto pool_ptr = pool_.lock()) {
        try {
          (*pool_ptr.get())->add(std::unique_ptr<T>{ptr});
          return;
        } catch(...) {}
      }
      std::default_delete<T>{}(ptr);
    }
   private:
    std::weak_ptr<SharedPool<T>* > pool_;
  };

 public:
  using ptr_type = std::unique_ptr<T, External_Deleter >;

  SharedPool() : this_ptr_(new SharedPool<T>*(this)) {}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.Push(std::move(t));
  }

  ptr_type acquire() {
    assert(!pool_.empty());
    ptr_type tmp(pool_.top().release(),
                 External_Deleter{std::weak_ptr<SharedPool<T>*>{this_ptr_}});
    pool_.pop();
    return std::move(tmp);
  }

  bool empty() const {
    return pool_.empty();
  }

  size_t size() const {
    return pool_.size();
  }

 private:
  std::shared_ptr<SharedPool<T>* > this_ptr_;
  std::stack<std::unique_ptr<T> > pool_;
};
_
17
swalog

これは、プールがまだ生きているかどうかをチェックするカスタム削除ツールです。

template<typename T>
class return_to_pool
{
  std::weak_ptr<SharedPool<T>> pool

public:
  return_to_pool(const shared_ptr<SharedPool<T>>& sp) : pool(sp) { }

  void operator()(T* p) const
  {
    if (auto sp = pool.lock())
    {
      try {
        sp->add(std::unique_ptr<T>(p));
        return;
      } catch (const std::bad_alloc&) {
      }
    }
    std::default_delete<T>{}(p);
  }
};

template <class T>
class SharedPool : std::enable_shared_from_this<SharedPool<T>>
{
public:
  using ptr_type = std::unique_ptr<T, return_to_pool<T>>;
  ...
  ptr_type acquire()
  {
    if (pool_.empty())
      throw std::logic_error("pool closed");
    ptr_type tmp{pool_.top().release(), this->shared_from_this()};
    pool_.pop();
    return tmp;
  }
  ...
};

// SharedPool must be owned by a shared_ptr for enable_shared_from_this to work
auto pool = std::make_shared<SharedPool<int>>();
9
Jonathan Wakely

質問は古く、すでに回答済みですが、@ swalogによって提案された解決策について1つのマイナーなコメントがあります。

削除ファンクターは、二重削除のためにメモリ破損を引き起こす可能性があります。

void operator()(T* ptr) {
  if (auto pool_ptr = pool_.lock()) {
    try {
      (*pool_ptr.get())->add(std::unique_ptr<T>{ptr});
      return;
    } catch(...) {}
  }
  std::default_delete<T>{}(ptr);
}

unique_ptrここで作成されたものは、例外がキャッチされると破棄されます。したがって、

std::default_delete<T>{}(ptr);

二重削除になります。

T *からunique_ptrを作成する場所を変更することで修正できます。

void operator()(T* ptr) {
  std::unique_ptr<T> uptr(ptr);
  if (auto pool_ptr = pool_.lock()) {
    try {
      (*pool_ptr.get())->add(std::move(uptr));
      return;
    } catch(...) {}
  }
}
3
fgg

代わりにshared_ptrの使用を検討してください。あなたがしなければならない唯一の変更はnot複数の所有者を持つ自動ポインタを数えることです。 SharedPoolから取得したオブジェクトは、通常どおり自動ポインターを削除できますが、SharedPoolは実際の自動ポインターを保持します。

template <class T>
class SharedPool
{
 public:
  SharedPool(){}
  virtual ~SharedPool(){}

  void add(std::unique_ptr<T> t) {
    pool_.Push_back(std::move(t));
  }

  std::shared_ptr<T> acquire() {
    assert(!empty());
    return *std::find_if(pool_.begin(), pool.end(), [](const std::shared_ptr<T>& i){return i.count() == 1;});
  }

  bool empty() const {
    return std::none_of(pool_.begin(), pool_.end(), [](const std::shared_ptr<T>& i){return i.count() == 1;});
  }

 private:
  std::vector<std::shared_ptr<T>> pool_;
};
0
Jonathan Mee