C++訳分からなすぎワロタ (2) なぜ shared_ptr のcopy ctorはconst参照を取るのか

auto_ptr の場合は,やや聞き慣れない "move ctor" しか無くて,それは非const参照を取るよね.shared_ptr の場合でも,copy constructと言いつつカウンタをインクリメントするんだから非constになるんじゃないのか...? と不思議に思った.

    template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )

    shared_ptr( shared_ptr<Y> const & r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )

#else

    shared_ptr( shared_ptr<Y> const & r )

#endif
    : px( r.px ), pn( r.pn ) // never throws
    {
    }

で, px が生ポインタで pn がカウンタ (boost::detail::shared_count) なのでそっちの copy ctor を見る:

    shared_count(shared_count const & r): pi_(r.pi_) // nothrow
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        , id_(shared_count_id)
#endif
    {
        if( pi_ != 0 ) pi_->add_ref_copy();
    }

デバッグ用を除けば)単一のメンバ sp_counted_base * pi_ のみを持ち,それを共有して操作するラッパだった.よって最初の問いの答えとしては「参照カウンタは『constなポインタ』の先にぶら下がっており,ポインタが指す先が動く訳ではないので引数はconst参照であって良い」というものだった.const sp_counted_base * pi_ と定義されてた訳じゃないからね.まぁ,色んなところで出る話だ... 「constって何?」「deep vs shallow」とか,その辺.
折角なのでポインタの先で共有されてるカウンタ・オブジェクトの実体 sp_counted_base * pi_ において何が(破壊的に)行われてるのか見ていこう.しかしここからはアーキテクチャ依存なので*1,最近の私はWindows環境が専門のプログラマである*2都合上,とりあえずWin32における実装を見る:

    void add_ref_copy()
    {
        BOOST_INTERLOCKED_INCREMENT( &use_count_ );
    }

ネストがいい加減 深過ぎるので,これはWin32 API InterlockedIncrement*3 に展開されるマクロ*4だと指摘するにとどめる.そしてそのAPIの中身はx86のアトミック命令 lock add なのであった.(今回は ifdef の,最も代表的と思われる場合だけに注目したが.)
いや,まぁそんなことは最初から想像が付いていたのだが... まとめの感想としては,C++の初期化・コピー・代入って難しいなー.(そしてBoostの実装はGNU Autotools並のややこしさだなー)

*1:http://www.boost.org/doc/libs/1_45_0/boost/smart_ptr/detail/sp_counted_base.hpp

*2:ここで「Windowsプログラマ」と言ったら「人生を書き換えたってのはあなたでしたか?」ということになってしまう.言葉遣いは正確に.

*3:http://msdn.microsoft.com/ja-jp/library/cc429236.aspx

*4:http://www.boost.org/doc/libs/1_45_0/boost/detail/interlocked.hpp