前回の記事std::unique_ptrには ->* 演算子がない!にて作ったものを改良して、色々なスマートポインタに->*演算子を追加するクラスを作りました。また、今回、上の記事を読まなくても大丈夫な内容になっています。
あまり使ったことがないという方が大多数だと思いますので(わたしもですが)、忘れてた方もああそんなもんもあったなと思い出せる程度に紹介いたします。
- ->*演算子とは
間接メンバポインタ演算子というもので、メンバ変数 / メンバ関数へのポインタを受け取ってそれを解決してくれる演算子です。メンバ変数 / メンバ関数へのポインタというのは、クラスの頭からそのメンバ変数やメンバ関数までのオフセットみたいなものです。thisポインタにオフセットを加算してくれる演算子みたいな感じですね。
foo* f = new foo();
int foo::* foobar = &foo::bar;
f->*foobar= 10; // メンバ変数への間接参照
void ( foo::* foobaz)() = &foo::baz;
( f->* barbaz )(); // メンバ関数を間接参照して呼び出し
void ( foo::* fooqux )() const = &foo::qux;
( f->* fooqux )(); // constメンバ関数を間接参照して呼び出し
void ( foo::* const fooquux )() const = &foo::quux; // constポインタ
( f->* fooqux )();
fooquux = fooqux; // oops! quux is qualified as cosntant type
構文としてはこんな感じです。変態的な文法の多いC++でも気持ち悪い部類です。メンバ関数がオーバーロードされていてかつ対象が推論できないときは、目的の型にキャストしてあげる必要があります。
さて、そろそろ影の薄い演算子も掘り起こされてきた頃合いだと思うので本題に入ります。
さきに、実際のコードで使われるメタ関数を載せます。
// std::conditionalの高階メタ関数
template < class _Test, class _Ty1, class _Ty2 >
using conditional_t = typename std::conditional< _Test::value, _Ty1, _Ty2 >::type;
// std::true_typeかstd::false_typeを受け取って論理和を取る
template < class A, class... Args >
struct meta_or
: conditional_t < A, std::true_type, meta_or < Args... > >
{
};
template < class A >
struct meta_or < A > : A
{
};
以下がそのコードです。ptrに基となるスマートポインタを指定します。
#include <memory>
#include <type_traits>
template < class ptr >
class ex_smart_ptr
: public ptr
{
using elem_t = typename ptr::element_type;
public:
using ptr::ptr;
// constメンバ関数バージョン
template < class Ret, class... Args >
auto operator ->* ( Ret( elem_t::* mem_ptr ) ( Args... ) ) const
{
return [this, mem_ptr] ( Args&&... args )
{
return ( ptr::get()->*mem_ptr )( std::forward < Args >( args )... );
};
}
// 非constメンバ関数バージョン
template < class Ret, class... Args >
auto operator ->* ( Ret( elem_t::* mem_ptr ) ( Args... ) const ) const
{
return [this, mem_ptr] ( Args&&... args )
{
return ( ptr::get()->*mem_ptr )( std::forward < Args >( args )... );
};
}
// メンバ変数バージョン
template < class Uz >
auto operator ->* ( Uz elem_t::* mem_ptr ) const
-> conditional_t < meta_or < std::is_const < elem_t >, std::is_const < Uz > >, const Uz, Uz >&
{
return ptr::get()->*mem_ptr;
}
};
関数用の->*では、ラムダ式が返るようになっています。引数のメンバ関数ポインタがconst修飾されているかどうかでオーバーロードしなければなりません。
変数用の->*では、変数への参照が返ります。const修飾のせいで戻り値が少し長くなってしまっています。
ポインタのconst修飾は少しわかりづらいですので簡単にまとめてみました。
raw pointer | smart pointer | せつめい |
---|---|---|
int* rp; | std::unique_ptr < int > sp; | const修飾なし |
int* const rp; | const std::unique_ptr < int > sp; | constポインタ |
const int* rp; | std::unique_ptr < const int > sp; | const修飾された値を指すポインタ |
int const* rp; | 同上 | 同上 |
const int* const rp; | const std::unique_ptr < const int > sp; | const修飾された値を指すconstポインタ |
int const* const rp; | 同上 | 同上 |
スマートポインタのほうが分かりやすいですね。
戻り値にconst修飾をつけるのはconst修飾された値を指すポインタ下4つというわけですね。
もうひとつ、メンバがconst修飾されていれば戻り値にconst修飾をつけなければなりません。
struct foo
{
const int bar;
};
こういう場合です。当然ですね。よって、参照先にconst修飾がかかる場合、または、メンバがconst修飾されている場合に戻り値にconst修飾をすればいいわけです。これを実現するメタ関数がちょっと長い戻り値というわけです。
さて、これを実際に使うコードは以下になります。
template < class Ty, class Deleter = std::default_delete < Ty > >
using unique_ptr = ex_smart_ptr < std::unique_ptr < Ty, Deleter > >;
template < class Ty >
using shared_ptr = ex_smart_ptr < std::shared_ptr < Ty > >;
template < class Ty >
using weak_ptr = ex_smart_ptr < std::weak_ptr < Ty > >;
汎用的な記述が可能になりました。
std::unique_ptrと他2つではテンプレート引数が異なるのでその差を吸収するのがこの部分になります。
標準でないスマートポインタに対応させようと思うのなら、element_typeとget()関数をそれぞれ橋渡しするものを作る必要があります。或いはtraitsを前提にしてex_smart_ptrを書き直した方が楽かもしれません。
ここまでやってなんですが、->*演算子を使う機会ってあまりないんですよね。
なにかの参考になれば幸いです。