♻️
enable_shared_from_this の使い方メモ
enable_shared_from_this
は shared_ptr
で管理したいクラスのベースにするやつだが、これをそのまま使うのは非常に危険が危ない。
危ない理由一覧
- 絶対shared_ptr管理するためにファクトリ関数以外から
new
とかされたくない - コピーとかもされたくない
- 多重継承したときにやばい
- etc...
- C++17で多少マシにはなったけどまだまだ危険が危ない
参考資料
なので 絶対shared_ptrで管理させるマン
なるいい感じのやつ作ってみた。
shared_base.hpp
shared_base.hpp
#pragma once
#include <memory>
#include <type_traits>
namespace util {
// SFINAE用
template <bool val>
using en_if = typename std::enable_if<val, std::nullptr_t>::type;
// メンバポインタのshared_ptrを取得する
template <class T, class U, class M, en_if<std::is_base_of<U, T>::value> = nullptr>
auto mem_ptr(const std::shared_ptr<T> &p, M U::*pm)
-> std::shared_ptr<typename std::remove_reference<decltype(p.get()->*pm)>::type>
{
return p == nullptr || pm == nullptr ? nullptr
: decltype(mem_ptr(p, pm))(p, &(p.get()->*pm));
}
// 内部クラス
// これを直接継承しない
class virtual_shared_base: public std::enable_shared_from_this<virtual_shared_base>
{
protected:
// 外部でインスタンス化されないようコンストラクタは全部protected
// 派生先でもprotectedにしておくこと
// ※ make_sharedで使うからprivateにはしない
virtual_shared_base() = default;
public:
// 全部shared_ptr管理したいのでコピーもムーブも禁止
// 派生先でも定義しないこと
virtual_shared_base(const virtual_shared_base&) = delete;
virtual_shared_base(virtual_shared_base&&) = delete;
virtual_shared_base& operator = (const virtual_shared_base&) = delete;
virtual_shared_base& operator = (virtual_shared_base&&) = delete;
virtual ~virtual_shared_base() = default;
private:
// SFINAE用
template <class T>
using base_check = en_if<std::is_base_of<virtual_shared_base, T>::value>;
protected:
// std::make_sharedの代わり
// 派生先のファクトリ関数から使う用
// ※ publicコンストラクタがないとstd::make_sharedが使えないため
template <class T, class ...Arg, base_check<T> = nullptr>
static std::shared_ptr<T> make_shared(Arg &&...arg) {
struct Dummy: T {
Dummy(Arg &&...arg): T(std::forward<Arg>(arg)...) { }
};
return std::make_shared<Dummy>(std::forward<Arg>(arg)...);
}
// shared_from_this()の代わり
// ダウンキャストして自身の型のshared_ptrを取る
// get_shared(this) の様に使う
template <class T, base_check<T> = nullptr>
static std::shared_ptr<T> get_shared(T *self) {
return self == nullptr ? nullptr
: std::shared_ptr<T>(self->virtual_shared_base::shared_from_this(), self);
}
// メンバポインタの取得
// mem_ptr(this, &TypeName::memberName) の様に使う
template <class T, class U, class M, base_check<T> = nullptr>
static auto mem_ptr(T *self, M U::*pm)
-> decltype(util::mem_ptr(get_shared(self), pm))
{
return util::mem_ptr(get_shared(self), pm);
}
};
// enable_sahred_from_thisをいい感じにラップしたやつ
// enable_sahred_from_thisの代わりにこれを継承する
template <class T>
class shared_base: virtual public virtual_shared_base
{
protected:
shared_base() = default;
public:
std::shared_ptr<T> shared_from_this() {
return get_shared(static_cast<T*>(this));
}
std::shared_ptr<const T> shared_from_this() const {
return get_shared(static_cast<const T*>(this));
}
};
} // namespace util
使い方
-
enable_sahred_from_this
と同じようにshared_base
をpublic
継承する - コンストラクタを必ず
protected
にする - 静的メンバ関数とかフレンド関数とかのファクトリメソッドから
make_shared
使って生成する - thisポインタは
get_shared(this)
, メンバのポインタはmem_ptr(this, &T::mem)
で取るようにして絶対に生のポインタを外に出さないこと
sample.cpp
#include <iostream>
#include <string>
#include <functional>
#include "shared_base"
class Base1: public util::shared_base<Base1>
{
public:
int val;
// ファクトリ関数
static std::shared_ptr<Base1> Create(int val) {
return make_shared<Base1>(val);
}
protected:
// コンストラクタはprotectedで
Base1(int v): val(v) { }
};
class Base2: public util::shared_base<Base2>
{
// コンストラクタは必ずprotectedにすること
protected:
Base2() = default;
};
// 多重継承してもOK
class C: public Base1, public Base2 {
public:
std::string str;
static std::shared_ptr<C> Create(int v, std::string s) {
return make_shared<C>(v, s);
}
protected:
C(int v, std::string s): Base1(v), str(s) { }
public:
// メンバポインタ取得
std::shared_ptr<int> get_val_ref() {
// &(this->val) としたくなるが絶対やらないこと
return mem_ptr(this, &C::val);
}
std::function<std::shared_ptr<int>()> get_func() {
// 絶対に生のthisポインタをbindしないこと
return std::bind(&C::get_val_ref, get_shared(this));
}
std::function<std::string()> get_str_func() const {
// 絶対にthisキャプチャしないこと
return [self = get_shared(this)]() { return self->str; };
}
};
int main()
{
using namespace std;
shared_ptr<Base1> base1 = Base1::Create(1);
shared_ptr<C> c = C::Create(4, "hoge");
{
shared_ptr<const C> cc = c;
shared_ptr<int> v = c->get_val_ref();
// &(cc->str) みたいに生のポインタを取らないこと
shared_ptr<const string> cstr = util::mem_ptr(cc, &C::str);
*v += 1;
cout << base1->val << endl; // 1
cout << *v << endl; // 5
cout << cc->val << endl; // 5
cout << *cstr << endl; // hoge
cout << c.use_count() << endl; // 4
auto f = cc->get_str_func();
cout << f() << endl; // hoge
cout << c.use_count() << endl; // 5
}
cout << c.use_count() << endl; // 1
base1 = c;
cout << base1->val << endl; // 5
cout << c.use_count() << endl; // 2
cout << *c->get_func()() << endl; // 5
cout << c.use_count() << endl; // 2
// publicなコンストラクタは無いはずなので実体化できない
// Base2 _b2;
// C _c(0, "");
// コピーもムーブもさせない
// C _tmp = *c;
// C _tmp2 = std::move(*c);
// 参照は取れる
C &tmp = *c;
C &&tmp2 = std::move(*c);
}
Discussion