3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

std::unique_ptrをboost::optionalで改良する

Posted at

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と同じように使えます。ただし、getresetのオーバーロードの一部、比較演算子など不要になったメンバ関数が用意されていませんので注意してください。

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

間違いや改善点などご指摘がございましたらコメントをお願いいたします。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?