我正在尝试实施类似于the first described here的系统。也就是说,(ab)使用vtable修改来改变运行时的对象行为。这是我尝试在我正在开发的C ++项目中创建高效类型通用包装器的一部分。
该示例,如果您无法访问它,请使用memcpy()
和this
指针复制vtable:
void setType( const DataType& newType )
{
memcpy( this, &newType, sizeof(DataType) );
}
但是,我对此方法存在一个问题:我没有目标类的对象来复制vtable,也不想创建一个,因为某些类型的构造代价很高。
有没有办法访问vtable,它将被放置到给定类的对象中而没有该类的对象?
如果它在某种程度上是可移植的,那将是更好的,但我基本上已经辞职,因为它是特定于编译器的;因此,如果没有其他选项,只接受GCC / G ++方法。我们还假设我只关心在相当标准的操作系统和体系结构上构建它。
我正在使用C ++ 11,应该以某种方式为此提供帮助。
编辑:我想要完全清楚,我知道这种行为是多么危险。尽管我的介绍可能有所建议,但我对这个想法更感兴趣,也许它在非常有控制的环境中应用它,而不是我对生产软件的好主意。
答案 0 :(得分:4)
struct many_vtable {
void(*dtor)(void*);
void(*print)(void const*,std::ostream&);
};
template<class T>struct tag_t{};
template<class T>constexpr tag_t<T> tag = {};
template<class T>
many_vtable const* make_many_vtable(tag_t<T>){
static const many_vtable retval = {
// dtor
[](void* p){
reinterpret_cast<T*>(p)->~T();
},
// print
[](void const*p, std::ostream& os){
os<<*reinterpret_cast<T const*>(p);
}
};
return &retval;
}
struct many {
many_vtable const* vtable=nullptr;
std::aligned_storage_t<100, alignof(double)> buff;
void clear(){if(vtable) vtable->dtor(&buff);vtable=nullptr;}
~many(){ clear(); }
many()=default;
many(many const&)=delete; // not yet supported
many& operator=(many const&)=delete; // not yet supported
explicit operator bool()const{return vtable!=nullptr;}
template<class T,class...Args>
void emplace(Args&&...args){
static_assert(alignof(T) <= alignof(double), "not enough alignment");
static_assert(sizeof(T) <= 100, "not enough size");
clear();
::new((void*)&buff) T(std::forward<Args>(args)...);
vtable=make_many_vtable(tag<T>);
}
friend std::ostream& operator<<(std::ostream& os, many const&m){
if(!m.vtable) return os;
m.vtable->print(&m.buff, os);
return os;
}
};
This is a manual vtable design. It can store anything up to 100 bytes whose alignment is less than a double that can be printed to a stream.
'Erasing' down to more (or different) operations is easy. For example, being able to copy/move to another many.
It does not violate the standard, and it has similar overhead to the linked example.
many m;
m.emplace<int>(3);
std::cout << m << '\n';
m.emplace<double>(3.14);
std::cout << m << '\n';
This is manual vtables, because we are basically reimplementing the vtable concept manually.