ObjectSquare [2009 年 9 月号]

[技術講座]


コラム2. enable_shared_from_this


株式会社 オージス総研
組み込みソリューション部
近藤 貴俊

前回、weak_ptrを利用するケースについて紹介しましたが、読者の方から、そのアプローチがboost::enable_shared_from_thisとして準備されているんだから、あわせてそれを紹介すべきでは?とのご指摘をいただきました。恥ずかしながらご指摘いただくまで、boost::enable_shared_from_thisについて十分理解しておりませんでした。そんなわけで、遅ればせながら今回ここで紹介させていただきます。アンケートにてコメントをよせていただいた皆様、ありがとうございました。

さて、boost::enable_shared_from_this(以降、enable_shared_from_this)は、まさに、自身を参照するweak_ptrを保持するためのメカニズムです。クラスContentを、weak_ptrではなく、enable_shared_from_thisを使う形に書き換えてみましょう。

#include <boost/enable_shared_from_this.hpp>
// 略
class Content:public DownloadObject, public boost::enable_shared_from_this<Content> {
public:
    static ContentSp create(std::string uri, int priority) {
        ContentSp s(new Content(uri, priority));
        return s;
    }
    virtual ~Content() { std::cout << "Destroyed" << std::endl; }
    virtual void downloadCompleted() {
        std::cout << "downloadCompleted" << std::endl;
        Cache::getInstance().addContent(shared_from_this());
    }
private:
    Content(std::string uri, int priority)
        :DownloadObject(uri, priority)
        { std::cout << "Created" << std::endl; }
};

enable_shared_from_thisを利用するためには、ヘッダファイルboost/enable_shared_from_this.hppをインクルードします。そして、利用するクラス(今回はContent)は、enable_shared_from_thisをpublic継承します。enable_shared_from_thisはクラステンプレートで、テンプレートパラメータには、利用するクラス(Content)を渡します。このように、継承元のクラステンプレートに、継承先クラスをテンプレートパラメータとして渡すテクニックをCRTPと呼びます。通常、継承元すなわち基底クラスは、継承先すなわち派生クラスの存在を意識しませんし、すべきではありません。しかし、今回のように派生クラスのshared_ptrが必要な場合、テンプレートパラメータを利用することで、直接派生クラスを意識することなくGenericに、派生クラスを扱うことができるのです。詳細なドキュメントはhttp://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/enable_shared_from_this.htmlで参照できます。

enable_shared_from_thisのクラス定義は、以下のようになっています(boost 1.39.0のコードから抜粋)。

namespace boost
{

template<class T> class enable_shared_from_this
{
protected:

    enable_shared_from_this()
    {
    }
    enable_shared_from_this(enable_shared_from_this const &)
    {
    }
    enable_shared_from_this & operator=(enable_shared_from_this const &)
    {
        return *this;
    }
    ~enable_shared_from_this()
    {
    }

public:
    shared_ptr<T> shared_from_this()
    {
        shared_ptr<T> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }
    shared_ptr<T const> shared_from_this() const
    {
        shared_ptr<T const> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

public: // actually private, but avoids compiler template friendship issues
    // Note: invoked automatically by shared_ptr; do not call
    template<class X, class Y> void 
        _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() )
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }

private:
    mutable weak_ptr<T> weak_this_;
};

} // namespace boost

privateなメンバ変数として、weak_this_を持っており、ここに、thisポインタのweak_ptrを格納します。このメンバ変数は、mutableとなっており、メンバ関数_internal_accept_ownerによって設定されます。キーワードmutableは、constなメンバ関数からも書き換え可能なメンバ変数を宣言するときに使います。例えば今回のように、クラスを意味論的にはイミュータブルに保ちながらも、実装では書き換えを行いたいケースです。通常、このようなクラスのメンバ変数は、コンストラクタの初期化リストで初期化を行い、その後は参照のみというイミュータブルな変数にしたいはずです。しかし、weak_ptrをコンストラクトするためには、shared_ptrが必要です。そして、shared_ptrをコンストラクトするためには、ターゲットとなるオブジェクトが必要です。今回、そのターゲットとなるオブジェクトがenable_shared_from_thisを継承するわけですから、enable_shared_from_thisのコンストラクト時にshared_ptrを渡すことができないわけです。

メンバ関数shared_from_thisは、weak_this_からshared_ptrを作り出して返します。利用者すなわちenable_shared_from_thisを継承したクラスは、shared_from_thisを呼び出すことで、自身のshared_ptrを取得することができるわけです。

ところで、メンバ変数weak_this_を設定するための_internal_accept_ownerは、誰が呼び出すのでしょうか?コメントにも書いてありますが、クラステンプレートshared_ptrから呼び出されます。アクセス制限がpublicとなっていますが、できればこのメンバ関数のアクセス制限はprivateとし、shared_ptrをフレンドクラスとして指定したいところです。ところが、shared_ptrはクラステンプレートであるため、フレンドクラス指定できないのです(C++の現在の最新規格であるISO/IEC14882-2003 7.1.5.3 Elaborated type specifiers [dcl.type.elab] パラグラフ2に記載されています)。なお、C++の次期規格である、C++0xでは、クラステンプレートもフレンド指定できるようになる予定です。

そんなわけで、アクセス制限がpublicとなっていますが、利用者向けのインターフェースでは無い旨がコメントで記載されているわけです。余談ですが、関数名_internal_accept_ownerがアンダースコアで始まっているのが、気になる人もいらっしゃるかもしれません。C++ではアンダースコアで始まり大文字が続く名前が予約されています。今回は、小文字が続いているので、予約された名前を使っていることにはならず、問題ありません。

訂正(ここから)

上記、_internal_accept_ownerという名前と、予約語に関する説明に不正確な点がありました。C++の規格では、以下のように規定されています。

ISO/IEC 14882:2003(E) 17.4.3.1.2 Global names [lib.global.names]

Certain sets of names and function signatures are always 
reserved to the implementation:
Each name that contains a double underscore (__) or begins with an underscore 
followed by an uppercase letter (2.11) is reserved to the implementation for any use.

Each name that begins with an underscore is reserved to the implementation for 
use as a name in the global namespace. *165

*165 Such names are also reserved in namespace ::std (17.4.3.1).

これを踏まえて考えると、_internal_accept_ownerは、アンダースコアで始まっているものの、その後に続くのは英小文字であり、さらに、名前が存在している場所がグローバル名前空間でもstd名前空間でもない、boost名前空間に存在するため、問題ないと言えます。

訂正(ここまで)

さて、_internal_accept_ownerの呼び出し側であるshared_ptrのコードも見てみましょう(shared_ptr.hpp)。sp_enable_shared_from_thisという関数が、2つオーバーロードされる形で準備されています。

// enable_shared_from_this support

template< class X, class Y, class T > 
inline void sp_enable_shared_from_this( 
    boost::shared_ptr<X> const * ppx, 
    Y const * py, 
    boost::enable_shared_from_this< T > const * pe )
{
    if( pe != 0 )
    {
        pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
    }
}

inline void sp_enable_shared_from_this( ... )
{
}

そして、shared_ptrのコンストラクタから、sp_enable_shared_from_thisが呼び出されています。

template<class T> class shared_ptr
{
public:
    // 略
    template<class Y>
    explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be complete
    {
        boost::detail::sp_enable_shared_from_this( this, p, p );
    }
    // 略

このように、関数sp_enable_shared_from_thisを経由することで、オーバーロードの引数パターンマッチングが行われます。1番目のsp_enable_shared_from_thisの定義における第3引数peの型に着目してください。boost::enable_shared_from_this< T >型になっていますね。そして、実引数pは、ユーザ定義のクラスのインスタンスへのポインタです。このクラスが、クラステンプレートenable_shared_from_thisをpublic継承していれば、この(1番目の)関数に適合します。それ以外であれば、可変長引数を持つ、2番目の関数に適合します。2番目の関数は、定義を見れば分かるように何も行いません。これによって、shared_ptrに設定するクラスが、enable_shared_from_thisを継承しているかどうかを判断して、適切に処理を分岐させているのです。

こんな仕組みに支えられて、enable_shared_from_thisは実現されています。

記事に戻る
© 2009 OGIS-RI Co., Ltd.
Index
Index