boost::scoped_ptr を魔改造してみた

boost::scoped_ptr は std::auto_ptr のより安全な代替として便利に使えるクラスですが、

  • release 出来ない
  • 削除関数を指定できない
  • 関数の戻り値として使えない
  • 配列の時は scoped_array を使わなければならない

等、いくつか不満点があります。


幸いなことに、 C++0x には これらの不満点が全て解決された std::unique_ptr がある訳ですが、
C++0x なんか待ってられない、便利なスマートポインタは今すぐ使いたいんだ、って人も多いはず。
実際、趣味で C++ をやってる僕のような人とは違い、仕事で C++ を使っている場合、
C++0x の規格が固まって、コンパイラも対応を進めて、 C++0x を使ったコーディングがメジャーになる、
それまでには、あと何年もかかるのは必至です。


というわけで、上記の不満点を解決した scoped_ptr を、ちょいちょいと作ってみました。
http://gist.github.com/713685


使い方は、こんな感じです:

#include "scoped_ptr.hpp"

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <boost/noncopyable.hpp>

struct hoge
  : private boost::noncopyable
{
  hoge( int i_ )
    : i(i_)
  {
    std::cout << "hoge (" << i << "): Hello!\n";
  }
  ~hoge() {
    std::cout << "hoge (" << i << "): Bye.\n";
  }
  
  void foo() {
    std::cout << "hoge (" << i << "): Foo!\n";
  }
  
  int i;
  
};

void basic_usage()
{
  {
    // 格納されたオブジェクトは、自動的に削除される
    etude::scoped_ptr<hoge> p( new hoge(1) );
    p->foo();
  }
  
  {
    etude::scoped_ptr<hoge> p1( new hoge(2) );
    
    // move() を使うことで所有権を移動できる( move すると空になる)
    etude::scoped_ptr<hoge> p2( p1.move() );
    BOOST_ASSERT( !p1 && p2 );
    // etude::scoped_ptr<hoge> p2 = p1.move(); とは残念ながら書けない
    
    // でも代入演算は素直に書ける(けど const に出来ないから一長一短)
    p1 = p2.move();
    BOOST_ASSERT( p1 && !p2 );
    // boost::scoped_ptr みたいに swap するとか。
    swap( p1, p2 ); // p1.swap(p2); でもいい
    BOOST_ASSERT( !p1 && p2 );
    
    // foo!
    p2->foo();
  }
  
  {
    // 格納しているオブジェクトを解放することも出来る。
    etude::scoped_ptr<hoge> p( new hoge(3) );
    hoge* const p_ = p.release();
    BOOST_ASSERT( !p );
    
    // 忘れずに delete してあげないとあげないと
    delete p_;
  }
}


void advanced_usage();

// ファクトリ。戻り値は move_ptr<T> を使う
etude::move_ptr<hoge> create_hoge( int i ) {
  return etude::scoped( new hoge(i) ); // move_ptr を作るヘルパ関数
}
// deleter
struct file_closer
{
  void operator()( std::FILE* file ) const {
    std::fclose( file );
  }
};

void advanced_usage()
{
  {
    // ファクトリを使ってオブジェクトをつくると、安全に取り扱える
    etude::scoped_ptr<hoge> p( create_hoge(4) );
    create_hoge(5); // ちゃんと解放される!
    
    // foo!
    p->foo();
  }
  {
    // テンプレート引数に T[] を指定すれば、配列を格納することも出来る
    etude::scoped_ptr<int[]> a( new int[6] );
    // operator[] と operator+ (こっちは微妙だが)が用意されている
    a[0] = 0;
    std::fill( a+1, a+6, 1 );
    BOOST_ASSERT( a[1] == 1 );
    // 削除は勿論 delete [] が呼ばれる
  }
  {
    // カスタムデリータを使った例。
    etude::scoped_ptr<std::FILE, file_closer> const f_( std::tmpfile() );
    std::FILE* const f = f_.get();  // 簡便のために
    
    // 適当に読み書き
    std::fprintf( f, "hogehoge" );
    // and so on...
    
    // 自動的に fclose される
  }
}

int main()
{
  basic_usage();
  advanced_usage();
}


実装は結構トリッキーなので、コンパイラによっては動かないかもしれません。
特に move_ptr の値渡し周りは、これで正しいかどうかが不安だったり。
というわけで、動かないようなら std::auto_ptr の実装を参考に、自前でなんとかしてください><

// っていうか http://home.roadrunner.com/~hinnant/unique_ptr03.html を使えばいんじゃね的な