svn 1.7 系にしてみたら psvn.el (だけでなく vc-svn の方もですけど) が ちゃんと動かない模様.たぶん 1.7 から作業コピーの構造が変わったせいで しょう.
公式に対応されるまでのとりあえずの回避策ということで,ここ の修正版を 使わせてもらうことにしました.
shared_ptr といいつつ,今回は shared_ptr のお供であるところの
weak_ptr を書いてみます.
weak_ptr というのは要するに所有権を持たない shared_ptr なので,
自分が参照している対象の所有者が 0 になったとき,自分も対象のポインタ
を参照できなくなるようにします.
参照している所有者の数は shared_count クラスで管理しているわけです
から, weak_ptr もこれを共有しつつ所有者数が 0 になっていないかを確
認すれば良いわけです.
しかし,現状の shared_count クラスでは,所有者の数が 0 になった瞬間
に自分自身を破棄しているので,これでは weak_ptr 側から所有者数を
チェックする際に無効なインスタンスにアクセスしてしまうので具合がよろ
しくありません.
ということで shared_count を次のように変更してみます. weak_ptr
からの参照数を管理するようにし,所有数が 0 になったときではなく,
weak_ptr からの参照数が 0 になったときに自分を破棄するようにしました.
struct shared_count { long count_; long weak_count_; shared_count() :count_(1), weak_count_(1) {} virtual ~shared_count() {} void increment() { ++count_; } void decrement(void* p) { if (--count_ == 0) { release(p); dec_weak_ref(); } } void inc_weak_ref() { ++weak_count_; } void dec_weak_ref() { if (--weak_count_ == 0) { destroy(); } } virtual void destroy() { delete this; } virtual void release(void*) const = 0; };
ここまでくれば,あとは shared_ptr と同じ要領で weak_ptr が実装で
きます. boost::weak_ptr の introduction にもあるとおり,
weak_ptr 自身からは直接ポインタにアクセスせず shared_ptr に変換し
てからアクセスするので,この変換するタイミングで所有者数をチェックす
るようにします.
ということで weak_ptr から shared_ptr へ変換するコンストラクタは
こんな具合で.
template <typename Y> shared_ptr(const weak_ptr<Y>& r) :ptr_(r.ptr_), count_(r.count_) { if (!count_ || count_->count_ == 0) { throw bad_weak_ptr(); } else { count_->increment(); } }
weak_ptr の実装はこんな感じに.
template<class T> class weak_ptr { T* ptr_; shared_count* count_; template <typename Y> friend class shared_ptr; template <typename Y> friend class weak_ptr; public: typedef T element_type; weak_ptr() :ptr_(0), count_(0) {} ~weak_ptr() { if (count_) count_->dec_weak_ref(); } weak_ptr(const weak_ptr& r) :ptr_(r.ptr_), count_(r.count_) { if (count_) count_->inc_weak_ref(); } template<class Y> weak_ptr(const weak_ptr<Y>& r) :ptr_(r.ptr_), count_(r.count_) { if (count_) count_->inc_weak_ref(); } template<class Y> weak_ptr(const shared_ptr<Y>& r) :ptr_(r.ptr_), count_(r.count_) { if (count_) count_->inc_weak_ref(); } weak_ptr& operator = (const weak_ptr& r) { if (count_ != r.count_) { weak_ptr(r).swap(*this); } return *this; } template<class Y> weak_ptr& operator = (const weak_ptr<Y>& r) { if (count_ != r.count_) { weak_ptr(r).swap(*this); } return *this; } template<class Y> weak_ptr& operator = (const shared_ptr<Y>& r) { if (count_ != r.count_) { weak_ptr(r).swap(*this); } return *this; } long use_count() const { return count_ ? count_->count_ : 0; } bool expired() const { return use_count() == 0; } shared_ptr<T> lock() const { return expired() ? shared_ptr<T>() : shared_ptr<T>(*this); } void reset() { weak_ptr().swap(*this); } void swap(weak_ptr<T>& r) { std::swap(ptr_, r.ptr_); std::swap(count_, r.count_); } };
weak_ptr 自身ではポインタに触れないものの shared_ptr に変換する際
に必要になる兼ね合いでポインタをメンバに含んでいるのがちょっと勿体ないような気もしますね…
なにはともあれ,これでひとまず最低限それっぽい shared_ptr の機能は
揃って一区切りかなー? といった感じですね.
次はカスタムデリータやアロケータを受け取るバージョンの コンストラクタ を実装してみます.(だんだんソース全部載せるのも長ったらしく感じてきたの で,今回は追加変更する部分だけコードを載せることにします)
まずカスタムデリータを渡す場合だと delete してるところを置き換えれ
ばいいので,こんな感じに shared_count を派生すれば良さそうです.
template <typename T, typename D> struct shared_count_with_deleter :public shared_count { D deleter_; shared_count_with_deleter(D d) :deleter_(d) {} virtual void release(void* p) const { deleter_(reinterpret_cast<T*>(p)); } };
コンストラクタはこんな感じ.
template<typename Y, typename D> shared_ptr(Y * p, D d) :ptr_(p), count_(new shared_count_with_deleter<Y, D>(d)) { }
次にカスタムアロケータを渡す場合を考えてみます.やることは
shared_count の生成破棄をアロケータ経由で行うようにすればいいという
ことなので,コンストラクタの実装はカスタムアロケータで確保したメモリ
に対して placement new で shared_count を生成します.
template<typename Y, typename D, typename A> shared_ptr(Y * p, D d, A a) :ptr_(p), count_(0) { typedef shared_count_with_deleter_allocator<Y, D, A> sc_type; typename A::template rebind<sc_type>::other a2(a); count_ = a2.allocate(1); new (static_cast<void*>(count_)) sc_type(d, a); }
placement new によって生成さたので shared_count 側で自分自身を破棄
するのに delete 演算子は使えません.というわけで自分自身を破棄する
処理をカスタム化するために destroy メソッドを追加します.
struct shared_count { long count_; shared_count() :count_(1) {} virtual ~shared_count() {} void increment() { ++count_; } void decrement(void* p) { if (--count_ == 0) { release(p); destroy(); } } virtual void destroy() { delete this; } virtual void release(void*) const = 0; };
そしてアロケータ利用版の shared_count_with_deleter_allocator では
destroy で明示的にデストラクタを呼び,アロケータ経由でメモリを開放
するようにします.
template <typename T, typename D, typename A> struct shared_count_with_deleter_allocator :public shared_count { D deleter_; A allocator_; typedef shared_count_with_deleter_allocator<T,D,A> this_type; shared_count_with_deleter_allocator(D d, A a) :deleter_(d), allocator_(a) {} virtual void release(void* p) const { deleter_(reinterpret_cast<T*>(p)); } virtual void destroy() { typedef typename A::template rebind<this_type>::other A2; A2 a(allocator_); this->~this_type(); a.deallocate(this, 1); } };
いちおうちゃんと動いてるかなー?
参照カウントと削除子の二つ分もメモリ確保してるのはちょっと勿体ないなぁ というか,よく考えてみると,この二つは同じ寿命なので一つにまとめても 良さそうです.
ということで参照カウントと削除子を両方管理するような shared_count
クラスを用意してみたバージョンがこんな感じ.
struct shared_count { long count_; shared_count() :count_(1) {} void increment() { ++count_; } void decrement(void* p) { if (--count_ == 0) { release(p); delete this; } } virtual void release(void*) const = 0; }; template <typename T> struct shared_count_default :public shared_count { virtual void release(void* p) const { delete reinterpret_cast<T*>(p); } }; template <typename T> class shared_ptr { T* ptr_; shared_count* count_; public: typedef T element_type; shared_ptr() :ptr_(0), count_(0) {} ~shared_ptr() { if (count_) { count_->decrement(ptr_); } } template <typename Y> explicit shared_ptr(Y* p) :ptr_(p), count_(new shared_count_default<Y>) { } shared_ptr(const shared_ptr& r) :ptr_(r.ptr_), count_(r.count_) { if (count_) { count_->increment(); } } shared_ptr& operator = (const shared_ptr& r) { if (count_ != r.count_) { shared_ptr(r).swap(*this); } return *this; } T& operator * () const { return *ptr_;} T* operator -> () const { return ptr_; } long use_count() const { return count_ ? count_->count_ : 0; } T* get() const { return ptr_; } void swap(shared_ptr& r) { std::swap(ptr_, r.ptr_); std::swap(count_, r.count_); } void reset() { shared_ptr().swap(*this); } };
意味的に increment, decrement の処理は shared_count 側だろう思っ
て移しましたけど,そうすると decrement にポインタを渡さないといけな
くなってしまい,これはこれでちょっと意味が変な気もします.モヤモヤ
shared_ptr の コンストラクタの定義 を見るとテンプレート引数で指定し た T 型のポインタだけでなく, T* とコンパチなポインタ (たとえば T のサブクラスのポインタ) であれば渡すことができて,かつ
The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void.
とあるように,ベースクラスの shared_ptr にサブクラスのポインタを格 納した場合でも,デストラクタが仮想じゃなくてもサブクラスのデストラク タがちゃんと呼ばれるそうで.
ということで実装してみる.
こういうときは type erasure な手法を使えばいいらしいということで, 基本的なアイデアはこんな感じでいいのかな?
struct deleter_base { virtual void operator () (void*) const = 0; }; template <typename T> struct default_deleter :public deleter_base { virtual void operator () (void* p) const { delete reinterpret_cast<T*>(p); } };
こんな感じにテンプレートで型を保持しておけば正しい型で delete できる.
struct base { // not virtual ~base() {std::cout << "base.dtor" << std::endl;} }; struct derived :public base { ~derived() {std::cout << "derived.dtor" << std::endl;} }; base* ptr = new derived; default_deleter<derived>* del = new default_deleter<derived>; (*del)(ptr); // result is // > derived.dtor // > base.dtor
そんな感じのを踏まえて修正してみたバージョン
struct deleter_base { virtual void operator () (void*) const = 0; }; template <typename T> struct default_deleter :public deleter_base { virtual void operator () (void* p) const { delete reinterpret_cast<T*>(p); } }; template <typename T> class shared_ptr { T* ptr_; long* count_; deleter_base* deleter_; public: typedef T element_type; shared_ptr() :ptr_(0), count_(0), deleter_(0) {} ~shared_ptr() { decrement(); } template <typename Y> explicit shared_ptr(Y* p) :ptr_(p), count_(new long(1)), deleter_(new default_deleter<Y>) { } shared_ptr(const shared_ptr& r) :ptr_(r.ptr_), count_(r.count_), deleter_(r.deleter_) { increment(); } shared_ptr& operator = (const shared_ptr& r) { decrement(); count_ = r.count_; ptr_ = r.ptr_; deleter_ = r.deleter_; increment(); return *this; } T& operator * () const { return *ptr_;} T* operator -> () const { return ptr_; } long use_count() const { return count_ ? *count_ : 0; } T* get() const { return ptr_; } void swap(shared_ptr& r) { std::swap(ptr_, r.ptr_); std::swap(count_, r.count_); std::swap(deleter_, r.deleter_); } void reset() { shared_ptr().swap(*this); } private: void decrement() { if (count_) { if (--(*count_) == 0) { delete count_; (*deleter_)(ptr_); delete deleter_; } } } void increment() { if (count_) { ++(*count_); } } };
まぁ期待通りに動いてるっぽい.たぶん.
[追記] 後で気付きましたけど,案の定このコードにはバグがあります.
興味本位というか練習のつもりで shared_ptr を自分で書いてみる.
まずは boost::shared_ptr のドキュメントにあるインタフェースを元にして
最低限のものを単純に書いてみた.(ということで例外安全とかスレッドセー
フとか難しそうなことはひとまず無視)
template <typename T> class shared_ptr { T* ptr_; long* count_; public: typedef T element_type; shared_ptr() :ptr_(0), count_(0) {} ~shared_ptr() { decrement(); } template <typename Y> explicit shared_ptr(Y* p) :ptr_(p), count_(new long(1)) { } shared_ptr(const shared_ptr& r) :ptr_(r.ptr_), count_(r.count_) { increment(); } shared_ptr& operator = (const shared_ptr& r) { decrement(); count_ = r.count_; ptr_ = r.ptr_; increment(); return *this; } T& operator * () const { return *ptr_;} T* operator -> () const { return ptr_; } long use_count() const { return count_ ? *count_ : 0; } T* get() const { return ptr_; } void swap(shared_ptr& r) { std::swap(ptr_, r.ptr_); std::swap(count_, r.count_); } void reset() { shared_ptr().swap(*this); } private: void decrement() { if (count_) { if (--(*count_) == 0) { delete count_; delete ptr_; } } } void increment() { if (count_) { ++(*count_); } } };
こういう const 型ヘのポインタを delete するのって合法なんですね.
const hoge* ptr = new hoge; delete ptr; // OK
5.3.5 delete のとこに書いてた.
[Note: a pointer to a const type can be the operand of a delete-expression; it is not necessary to cast away the constness (5.2.11) of the pointer expression before it is used as the operand of the delete-expression. ]
いまいち釈然としないなーと思ってテケトーにぐぐる先生に聞いてみてたら ここの説明でしっくりきた.たしかにスタックに const なオブジェクト構築 した場合でもデストラクタ呼べないかんし,そういうもんなんやね
http://stackoverflow.com/questions/755196/deleting-a-pointer-to-const-t-const