这种方法试图避免在缓冲区中创建虚拟对象。相反,它创建并扩展手动vtable。
我们首先要做的是一个价值vtable,它可以让我们虚拟地处理一个价值:
struct value_vtable {
void(* copy_ctor)(void* dest, void const* src) = nullptr;
void(* move_ctor)(void* dest, void* src) = nullptr;
void(* dtor)(void* delete_this) = nullptr;
};
为类型创建其中一个
T
看起来像这样:
template<class T>
value_vtable make_value_vtable() {
return {
[](void* dest, void const* src) { // copy
new(dest) T( *(T const*)src );
},
[](void* dest, void * src) { // move
new(dest) T( std::move(*(T*)src) );
},
[](void* delete_this) { // dtor
((T*)delete_this)->~T();
},
};
}
我们可以在线存储这些vtables,也可以为它们创建静态存储:
template<class T>
value_vtable const* get_value_vtable() {
static auto const table = make_value_vtable<T>();
return &table;
}
此时,我们没有存储任何内容。
这是一个value_storage。它可以存储任何值(可以复制、移动和销毁):
template<std::size_t S, std::size_t A>
struct value_storage {
value_vtable const* vtable;
std::aligned_storage_t<S, A> data;
template<class T,
std::enable_if_t<!std::is_same< std::decay_t<T>, value_storage >{}, int> =0,
std::enable_if_t< ( sizeof(T)<=S && alignof(T)<=A ), int > = 0
>
value_storage( T&& tin ) {
new ((void*)&data) std::decay_t<T>( std::forward<T>(tin) );
vtable = get_value_vtable<std::decay_t<T>>();
}
// to permit overriding the vtable:
protected:
template<class T>
value_storage( value_vtable const* vt, T&& t ):
value_storage( std::forward<T>(t) )
{
vtable = vt;
}
public:
void move_from( value_storage&& rhs ) {
clear();
if (!rhs.vtable) return;
rhs.vtable->move_ctor( &data, &rhs.data );
vtable = rhs.vtable;
}
void copy_from( value_storage const& rhs ) {
clear();
if (!rhs.vtable) return;
rhs.vtable->copy_ctor( &data, &rhs.data );
vtable = rhs.vtable;
}
value_storage( value_storage const& rhs ) {
copy_from(rhs);
}
value_storage( value_storage && rhs ) {
move_from(std::move(rhs));
}
value_storage& operator=( value_storage const& rhs ) {
copy_from(rhs);
return *this;
}
value_storage& operator=( value_storage && rhs ) {
move_from(std::move(rhs));
return *this;
}
template<class T>
T* get() { return (T*)&data; }
template<class T>
T const* get() const { return (T*)&data; }
explicit operator bool() const { return vtable; }
void clear() {
if (!vtable) return;
vtable->dtor( &data );
vtable = nullptr;
}
value_storage() = default;
~value_storage() { clear(); }
};
这种类型存储的内容可能像一个值,大小不限
S
并对齐
A
。它不存储它存储的类型,这是其他人的工作。它确实存储了如何复制、移动和销毁存储的任何内容,但它不知道存储的内容是什么。
它假定在块中构造的对象是在块的前面构造的。您可以添加
void* ptr
字段,如果您不想进行该假设。
现在我们可以增加这个
value_storage
操作。
特别是,我们希望演员能上场。
template<class Base>
struct based_value_vtable:value_vtable {
Base*(* to_base)(void* data) = nullptr;
};
template<class Base, class T>
based_value_vtable<Base> make_based_value_vtable() {
based_value_vtable<Base> r;
(value_vtable&)(r) = make_value_vtable<T>();
r.to_base = [](void* data)->Base* {
return (T*)data;
};
return r;
}
template<class Base, class T>
based_value_vtable<Base> const* get_based_value_vtable() {
static const auto vtable = make_based_value_vtable<Base, T>();
return &vtable;
}
现在我们已经扩展了
value_vtable
包括“vtable with base”系列。
template<class Base, std::size_t S, std::size_t A>
struct based_value:value_storage<S, A> {
template<class T,
std::enable_if_t< !std::is_same< std::decay_t<T>, based_value >{}, int> = 0
>
based_value( T&& tin ):
value_storage<S, A>(
get_based_value_vtable<Base, std::decay_t<T> >(),
std::forward<T>(tin)
)
{}
template<class T>
based_value(
based_value_vtable<Base> const* vt,
T&& tin
) : value_storage<S, A>( vt, std::forward<T>(tin) )
{}
based_value() = default;
based_value( based_value const& ) = default;
based_value( based_value && ) = default;
based_value& operator=( based_value const& ) = default;
based_value& operator=( based_value && ) = default;
based_value_vtable<Base> const* get_vt() const {
return static_cast< based_value_vtable<Base>* >(this->vtable);
}
Base* get() {
if (!*this) return nullptr;
return get_vt()->to_base( &this->data );
}
Base const* get() const {
if (!*this) return nullptr;
return get_vt()->to_base( (void*)&this->data );
}
};
Base
符合尺寸和对齐要求。
只需存储这些向量。这就解决了你的问题。这些对象满足以下值类型的公理:
std::vector
预期。
live example
代码没有经过严格测试(您可以看到非常小的测试),它可能仍然包含一些输入错误。但是这个设计很坚固,我以前做过。
使用进行扩充
operator*
和
operator->
如果您发现自己不止一次这样做,或者在您的
based_value
,比定制的更好的扩展
基本值
技巧是自动化vtable扩展过程。我会用类似这样的东西
type erasure with
std::any
,只需替换
any
使用
value_storage<Size, Align>
保证自动存储,添加更好
const
支持,并将两个vtable集成为一个(如
基本值
最后,我们会得到:
template<class T>
auto to_base = [](auto&& self)->copy_const< decltype(self), T& > {
return decltype(self)(self);
};
template<class Base, std::size_t S, std::size_t A>
using based_value = super_value_storage< S, A, decltype(to_base<Base>) >;
using my_type = based_value< Some_Base, 100, 32 >;
my_type bob = // some expression
if (bob)
return (bob->*to_base<Some_Base>)()
else
return nullptr;
或者类似的。
所有C样式的强制转换都可以用静态和常量强制转换的组合来替换,但我很懒。我认为我从来没有做过任何需要重新解释演员阵容的事情。
在您的
any_method
s、 现在,您可以将不同的对象映射到您必须支持的概念集。如果你知道如何对一个向量的狗进行鸡,你可以直接在
super_value_storage< Size, Align, ..., decltype(do_the_chicken) >
.
然而,这可能太过分了。