C++でコーディングしていると、「とりあえずオブジェクトの入る場所は確保しておきたいけど、今はまだオブジェクト自身を作りたくはない」という場面に時折出くわします。
そういう場合、ポインタと std::auto_ptr
(あるいは boost::scoped_ptr
)を使って、最初は 0
を入れておき、後からメモリ確保する、という方法が一般的ですが、時々、速度を上げる為にメモリ確保を行いたくない場合というのも存在したりしなかったり。
僕の場合は gintenlib::new_
や gintenlib::clonable_ptr
を boost::make_shared
に習って最適化する過程で、そのような状況にぶち当たりました。
そういう場合に使えるのが boost::optional
で、こいつは普通のオブジェクトに加え「無効値」という状態も管理してくれ、しかもその動作にメモリ確保は一切使わないという便利クラス。
しかしながら「予め確保した『オブジェクトの入る場所』に対してゴニョゴニョしたい」という場合には向きませんし、コピーコンストラクタを使わず直接オブジェクトを領域内に構築しようと思うと boost::in_place
を使わなければならないと、若干の不満点が残ります。
当然、これらは安全で信頼できるライブラリにするために必須なこと。なので Boost に文句を言うつもりは有りませんが、「少しくらい危険でも・・・」という需要も少なくはない筈。
というわけで、今日はそんなクラスを作ってみることにしました。
では早速、そのソースコードを見てみましょう:
#include <cassert> #include <boost/aligned_storage.hpp> #include <boost/noncopyable.hpp> namespace gintenlib { // デストラクタを呼び出すファンクタ struct destructor { typedef void result_type; template<typename T> void operator()( T* p ) const { p->~T(); } }; // struct destructor // T 型を収められるメモリ領域 template<typename T> struct storage : boost::aligned_storage<sizeof(T), boost::alignment_of<T>::value> {}; // ちょっと安全性の低い代わりに未初期化メモリアドレスを取得できる // boost::optional<T> 的ななにか template<typename T> struct optional_storage : private boost::noncopyable { typedef T element_type; typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; // 構築/破棄 optional_storage() : initialized_( false ) {} ~optional_storage() throw () { destory(); } // 中のデータを破棄する void destory() { destory( destructor() ); } // カスタムデストラクタを使ってデータを破棄する template<typename Destructor> void destory( Destructor d ) { if( initialized_ ) { d( get() ); initialized_ = false; } } // storage の生アドレスを得る void* address(){ return storage_.address(); } const void* address() const { return storage_.address(); } // 初期化ずみか否か bool initialized() const { return initialized_; } // 初期化ずみマークをつける void set_initialized() { initialized_ = true; } // 構築済みなら、構築されたオブジェクトのアドレスを取得する T* get() { return initialized() ? static_cast<T*>( address() ) : 0; } const T* get() const { return initialized() ? static_cast<const T*>( address() ) : 0; } // アロー演算 T* operator->() { using namespace std; assert( initialized() ); return get(); } const T* operator->() const { using namespace std; assert( initialized() ); return get(); } // 参照外し T& operator*() { using namespace std; assert( initialized() ); return *get(); } const T& operator*() const { using namespace std; assert( initialized() ); return *get(); } private: bool initialized_; typedef storage<T> storage_type; storage_type storage_; }; // optional_storage<T> } // namespace gintenlib
ふぅ。とりあえず部品自体は以上です。
使い方は、まぁざっとこんな感じ:
// さっきの続き #include <iostream> #include <boost/format.hpp> using namespace std; using boost::format; // テスト用オブジェクト class hoge : boost::noncopyable { int ID; public: hoge( int ID_ = 0 ) : ID( ID_ ) { cout << format( "%1%: hi!\n" ) % name(); } ~hoge() throw () { cout << format( "%1%: bye.\n" ) % name(); } string name() const { return ( format("hoge[%1%]") % ID ).str(); } void foo() { cout << format( "%1%: foo!\n" ) % name(); } }; // カスタムデストラクタ struct my_destructor : private gintenlib::destructor { typedef gintenlib::destructor base; typedef void result_type; template<typename T> void operator()( T* ptr ) { cout << "my_destructor is called.\n"; base::operator()( ptr ); } }; int main() { // とりあえず作るだけ { gintenlib::optional_storage<hoge> storage; } // 作って無意味にデストラクト { gintenlib::optional_storage<hoge> storage; storage.destory( my_destructor() ); } // ここまでのテストでは何も表示されないはず // オブジェクトを構築してみる // 未初期化メモリ領域を確保。オブジェクトはまだ作られない gintenlib::optional_storage<hoge> storage; // 作られた時点では未構築 assert( !storage.initialized() ); // 本来はここで、未初期化メモリを使っていたづらする // 構築 ::new ( storage.address() ) hoge( 1 ); // 構築できたら必ず set_initialized() をすぐに呼ぶ storage.set_initialized(); // 構築完了 assert( storage.initialized() ); // 適当にメソッドを呼んでみる // とりあえずポインタ的なインターフェイスで使える storage->foo(); // カスタムデストラクタで破棄してみる // なお明示的に破棄せずとも、デストラクタで自動破棄される storage.destory( my_destructor() ); // 破棄済み assert( !storage.initialized() ); // もういっかい破棄してみても何も起きないことを確認 storage.destory( my_destructor() ); }
実行結果:
hoge[1]: hi! hoge[1]: foo! my_destructor is called. hoge[1]: bye.
細かいことは・・・まぁ読めば分かるでしょう(投げやり
とにかく、確保したメモリにオブジェクトを構築する場合には placement new を使う、
placement new を呼んだら、即 set_initialized()
を呼ぶ、
この二点さえ気をつければ、後はそれなりに安全に ごにょごにょ できますよ、ということです。
あと、おまけの機能として、デストラクタ呼び出しをカスタマイズできるようになってますが、これはあんまり意味が無いかもしれません。あったら便利かもしれない、程度です。
というか、まぁ、普段はこんなの要らないのです。大体はスマートポインタで、効率を気にする場合は boost::optional
で何とかなるし。
結局、使い道は「コンストラクタを friend
指定したから placement new はこっちで呼ばせろゴルァ」とか「未初期化メモリ領域のポインタ欲しいです。用途? 秘密ですよウフフフ」とか、そういう怪しい感じになってしまうので、正直、あんまり役に立たないかなぁとも思います。
が、gintenlib::new_
の実装で使うので、折角だから銀天ライブラリの一員にしようかと思っている次第。
有意義な使い方を見つけた方はコメントお願いします。