std::unique_ptr
、ちゃんと使っていますか? ただ必要に応じてdeleteするだけでなく、デリータを自作することでscoped guardを実現することもできる素晴らしいクラスですが、scoped guardの思想を体現するには惜しい部分があります。
それは管理する型pointer
に対して必要以上の要求を課すこと、すなわちnullable pointerを要求することです。
これのせいで、何かリソースやハンドルなどポインタで定義されないものをstd::unique_ptr
で管理することはできません。せっかくカスタマイズできるのに。
nullable pointerは以下を満たす必要があります。
・EqualityComparable
・DefaultConstructible
・CopyConstructible
・CopyAssignable
・Destructible
加えて、bool
型に変換可能で、全ての動作が例外を投げる可能性がない必要があります。また、std::nullptr_t
型での初期化、std::nullptr_t
型を代入すること、std::nullptr_t
型との等価比較が可能でなければなりません。
しかし、完全なscoped guardクラスを実現するには、以下の条件だけで十分なはずです。
・CopyConstructible
・CopyAssignable
・NothrowDestructible
例外を投げないムーブがあるならコピーはできなくても大丈夫。
前置きが長くなりましたが、この記事ではこの不満を解消すべく、std::unique_ptr
型を改良したより汎用的なscoped_guard
クラスを作成したいと思います。
と言っても、std::unique_ptr
のnullable pointer型の部分を別の自作型で置き換えればいいだけなのでそんな大層な仕事はしません。ここではそのnullable型にboost::optional
を使うことにしました。
実装のコードの前に使い方のコードを示したいと思います。
型resource
がよくあるリソースクラスです。リソースは決まった手順で取得して、決まった手順で解放しなければなりません。
scoped_guard
にはテンプレート引数に管理する型と解放処理をするCallable型を必要とします。デリータはこのCallable型に変換可能でなければなりません。他はほとんどstd::unique_ptr
と同じように使えます。ただし、get
やreset
のオーバーロードの一部、比較演算子など不要になったメンバ関数が用意されていませんので注意してください。
#include <utility>
// リソース
struct resource ;
resource get_resource() ;
void release_res(resource res) ;
void res_dosomething(resource res) ;
int main()
{
using resource_type = scoped_guard< resource, void(*)(res) >;
resource_type r(get_res(), &release_res); // 生成と解放
if (r) { res_dosomething(*r); } // res_dosomethingが実行される。
// auto r2 = r; // コピーは認められない
auto r2 = std::move(r); // 所有権が移譲される。
if (r) { res_dosomething(*r); } // res_dosomethingは実行されない。
// res_dosomething(*r); // undefined behavior!
}
以下が実装のコードです。実際に使う場合は名前空間をちゃんとつけてあげなければなりませんね。
せっかくboostを使っているのにnoncopyable
を自作しているのは、boost::noncopyable
が色々と残念な仕様だからです。こちらで作ったnoncopyable
はテンプレート引数に自分の型を突っ込んであげなければなりません。
#include <utility>
#include <boost/optional.hpp>
#include <boost/operators.hpp>
// helpers
template< class T >
struct do_nothing { void operator()(const T&) noexcept {} };
template< class T >
class noncopyable
{
protected:
noncopyable() = default;
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
noncopyable(noncopyable&&) = default;
noncopyable& operator=(noncopyable&&) = default;
~noncopyable() = default;
};
// forward declaration
template< class T, class D = do_nothing< T > >
class scoped_guard ;
// implement
template< class T, class D >
class scoped_guard
: noncopyable< scoped_guard< T, D > >,
public boost::dereferenceable< scoped_guard< T, D >, T* >
{
boost::optional< T > element;
D deleter;
void release_element() noexcept { if (element) { deleter(*element); } }
public:
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using optional_type = boost::optional< value_type >;
using deleter_type = D;
// ctors and dtor
scoped_guard() = default;
scoped_guard(const_reference value, deleter_type d = deleter_type())
: element(value), deleter(d) {}
scoped_guard(value_type&& value, deleter_type d = deleter_type())
noexcept : element(std::move(value)), deleter(d) {}
scoped_guard(scoped_guard&& rhs) noexcept
: element(std::move(rhs.element)), deleter(std::move(rhs.deleter))
{ rhs.element = boost::none; }
scoped_guard& operator=(scoped_guard&& rhs) noexcept
{
element = std::move(rhs.element);
deleter = std::move(rhs.deleter);
rhs.element = boost::none;
return (*this);
}
~scoped_guard() noexcept { release_element(); }
// modifiers
optional_type release() noexcept
{
auto temp = std::move(element);
element = boost::none;
return temp;
}
void reset(const_reference value)
{
release_element();
element = value;
}
void reset(value_type&& value) noexcept
{
release_element();
element = std::move(value);
}
void swap(scoped_guard& other) noexcept
{
auto temp = std::move(*this);
*this = std::move(other);
other = std::move(temp);
}
// observers
deleter_type get_deleter() noexcept { return deleter; }
explicit operator bool() const noexcept
{ return static_cast< bool >(element); }
reference operator*() const noexcept
{ return *(const_cast< scoped_guard* >(this)->element); }
};
間違いや改善点などご指摘がございましたらコメントをお願いいたします。