非复制std::shared_ptr<boost::any>?
Non-copying std::shared_ptr<boost::any>?
我将 "instances of different types" 存储为 "shared ownership"。这就是我目前所做的:
class Destructible {
public:
virtual ~Destructible() = default;
};
// UGLY
class MyType1 : public Destructible { ... };
class MyTypeN : public Destructible { ... };
class Storage {
std::vector<std::shared_ptr<Destructible>> objects_;
...
}
我很乐意切换到 boost::any
,删除所有这些一致性并获得存储真正任何类型的实例的能力。我也喜欢 boost::any
界面和 boost::any_cast
.
但是我的类型不满足ValueType
要求,它们是不可复制的。这个问题最好的(最好是现有的)解决方案是什么?像 shared_any_ptr
这样的东西,它在创建时捕获析构函数,具有类型擦除、引用计数器并且可以做 any_cast
.
编辑:boost::any
允许通过移动创建,但我什至不希望移动和使用指针。
Edit2:我也广泛使用 make_shared
,所以 make_shared_any_ptr
会派上用场。
这对于共享指针并不棘手。我们甚至可以避免多次分配。
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>
而无需复制任何内容。
您会注意到 any_block_made
和 any_block_dtor
有多么相似。我相信这就是为什么 std 库中至少有一个主要的 shared_ptr 将删除器所在的位置重新用于 make_shared 本身。
我可能会做类似的事情,并在这里减少二进制文件的大小。此外,any_block_made
和 any_block_dtor
的 T
/D
参数实际上只是关于我们使用的内存块的大小和对齐方式,以及擦除的确切类型我存储在父级的 dtor
指针中的助手。带有 COMDAT 折叠(MSVC 或 GOLD)的 compiler/linker 可能会消除这里的二进制膨胀,但我可以自己做一些小心。
我将 "instances of different types" 存储为 "shared ownership"。这就是我目前所做的:
class Destructible {
public:
virtual ~Destructible() = default;
};
// UGLY
class MyType1 : public Destructible { ... };
class MyTypeN : public Destructible { ... };
class Storage {
std::vector<std::shared_ptr<Destructible>> objects_;
...
}
我很乐意切换到 boost::any
,删除所有这些一致性并获得存储真正任何类型的实例的能力。我也喜欢 boost::any
界面和 boost::any_cast
.
但是我的类型不满足ValueType
要求,它们是不可复制的。这个问题最好的(最好是现有的)解决方案是什么?像 shared_any_ptr
这样的东西,它在创建时捕获析构函数,具有类型擦除、引用计数器并且可以做 any_cast
.
编辑:boost::any
允许通过移动创建,但我什至不希望移动和使用指针。
Edit2:我也广泛使用 make_shared
,所以 make_shared_any_ptr
会派上用场。
这对于共享指针并不棘手。我们甚至可以避免多次分配。
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>
而无需复制任何内容。
您会注意到 any_block_made
和 any_block_dtor
有多么相似。我相信这就是为什么 std 库中至少有一个主要的 shared_ptr 将删除器所在的位置重新用于 make_shared 本身。
我可能会做类似的事情,并在这里减少二进制文件的大小。此外,any_block_made
和 any_block_dtor
的 T
/D
参数实际上只是关于我们使用的内存块的大小和对齐方式,以及擦除的确切类型我存储在父级的 dtor
指针中的助手。带有 COMDAT 折叠(MSVC 或 GOLD)的 compiler/linker 可能会消除这里的二进制膨胀,但我可以自己做一些小心。