これは共有ポインタでは難しいことではありません。複数の割り当てを避けることさえできます。
struct any_block {
any_block(any_block const&)=delete;
template<class T>
T* try_get() {
if (!info || !ptr) return nullptr;
if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr;
return static_cast<T*>(ptr);
}
template<class T>
T const* try_get() const {
if (!info || !ptr) return nullptr;
if (std::type_index(typeid(T)) != std::type_index(*info)) return nullptr;
return static_cast<T const*>(ptr);
}
~any_block() {
cleanup();
}
protected:
void cleanup(){
if (dtor) dtor(this);
dtor=0;
}
any_block() {}
std::type_info const* info = nullptr;
void* ptr = nullptr;
void(*dtor)(any_block*) = nullptr;
};
template<class T>
struct any_block_made:any_block {
std::aligned_storage_t<sizeof(T), alignof(T)> data;
any_block_made() {}
~any_block_made() {}
T* get_unsafe() {
return static_cast<T*>((void*)&data);
}
template<class...Args>
void emplace(Args&&...args) {
ptr = ::new((void*)get_unsafe()) T(std::forward<Args>(args)...);
info = &typeid(T);
dtor = [](any_block* self){
static_cast<any_block_made<T>*>(self)->get_unsafe()->~T();
};
}
};
template<class D>
struct any_block_dtor:any_block {
std::aligned_storage_t<sizeof(D), alignof(D)> dtor_data;
any_block_dtor() {}
~any_block_dtor() {
cleanup();
if (info) dtor_unsafe()->~D();
}
D* dtor_unsafe() {
return static_cast<D*>((void*)&dtor_data);
}
template<class T, class D0>
void init(T* t, D0&& d) {
::new((void*)dtor_unsafe()) D(std::forward<D0>(d));
info = &typeid(T);
ptr = t;
dtor = [](any_block* s) {
auto* self = static_cast<any_block_dtor<D>*>(s);
(*self->dtor_unsafe())(static_cast<T*>(self->ptr));
};
}
};
using any_ptr = std::shared_ptr<any_block>;
template<class T, class...Args>
any_ptr
make_any_ptr(Args&&...args) {
auto r = std::make_shared<any_block_made<T>>();
if (!r) return nullptr;
r->emplace(std::forward<Args>(args)...);
return r;
}
template<class T, class D=std::default_delete<T>>
any_ptr wrap_any_ptr(T* t, D&& d = {}) {
auto r = std::make_shared<any_block_dtor<std::decay_t<D>>>();
if (!r) return nullptr;
r->init(t, std::forward<D>(d));
return r;
}
あなたはany_cast
を実装する必要があると思いますが、try_get<T>
と、それは簡単なはずです。
const T
のようなコーナーケースがある場合があります。
template<class T>
std::shared_ptr<T>
crystalize_any_ptr(any_ptr ptr) {
if (!ptr) return nullptr;
T* pt = ptr->try_get<T>();
if (!pt) return nullptr;
return {pt, ptr}; // aliasing constructor
}
これは、あなたがany_ptr
を取ると種類が何かをコピーせずに一致する場合shared_ptr<T>
にそれを回すことができます。
live example。
any_block_made
とany_block_dtor
の類似度に気づくでしょう。私は、これが、標準ライブラリの少なくとも1つの主要なshared_ptrが、make_shared自身のためにDeleterが住んでいる場所を再利用する理由だと考えています。
私はおそらく同様のことができ、ここでバイナリサイズを減らすことができます。さらに、T
/D
のパラメータがany_block_made
とany_block_dtor
の場合、私たちが再生するメモリのブロックはどれくらい大きく、整列しているのですか?また、正確にタイプ消去されたヘルパーは、親のポインタdtor
に格納されています。 COMDATフォールディング(MSVCまたはGOLD)を使用したコンパイラ/リンカーは、ここではバイナリの肥大化を解消するかもしれませんが、ちょっとした注意を払って自分でやることができます。
あなた自身で 'any'と書いてください。それはすべてのタイプの消去の中で最もシンプルなので、良い演習です。 – Barry
@Barry Boostやどこかに問題の既存の解決策があるかどうかは不思議です。 – Anton3
あなたは 'boost :: variant'を使うことができます。これはまた、より安全な型です。 –