私は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(...)
、リソースをプールに戻します。
実装では、オブジェクトをプールに返すカスタム削除機能で_unique_ptr
_を使用します。 acquire
とrelease
はどちらも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つの方法(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_;
};
_
これは、プールがまだ生きているかどうかをチェックするカスタム削除ツールです。
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>>();
質問は古く、すでに回答済みですが、@ 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(...) {}
}
}
代わりに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_;
};