有没有办法从两个const ::std::type_info
个对象中确定,如果D描述的类型是从类型B派生的,那么我们将它们命名为B
和D
我问,因为我想要删除我得到的对象的类型,但稍后可以检查它是否可以安全地提升。
void* data;
const ::std::type_info* D;
template<typename D>
void store(D&& object)
{
D = &typeid(object);
data = ::std::addressof(object);
}
template<typename B>
B& load()
{
// if(typeid(B) != (*D)) throw ::std::bad_cast{};
return *reinterpret_cast<B*>(data); // <- also problematic
}
我希望能够像这样使用它:
class Base {};
class Derived : Base {};
Derived d;
store(d);
// ....
load<Base>();
因此,仅对typeids使用相等比较是不合适的。我很确定这可能会以类似于dynamic_cast可以解决这个问题的方式实现。我想要的是,在D&
可以分配给B&
允许B作为load()
的类型参数的每种情况下 - 当时不知道D
。
答案 0 :(得分:3)
我找到了让编译器和内部机制为我解决问题的方法。我不会遇到交叉编译问题,在这种情况下::std::type_info
也不一致。
typedef void (*throw_op)(void*);
throw_op dataThrow;
template<typename T>
[[ noreturn ]] void throwing(void* data)
{
throw static_cast<T*>(data);
}
[[ noreturn ]] void bad_cast()
{
throw ::std::bad_cast{};
}
template<typename B>
B& poly_load()
{
if(data == nullptr)
bad_cast();
try {
dataThrow(data);
} catch (B* ptr) {
return *ptr;
} catch (...) {
bad_cast();
}
}
您只需将以下行添加到商店操作:
dataThrow = throwing<D>;
这是如何工作的?它利用了例外以及如何捕获它们。请注意,这会使poly_load
多吗?比简单load
函数慢,因此我会保留两者。
C ++表示当抛出类型D*
的异常时,您可以使用catch-clause B*
捕获该异常,其中B
是D
的任何祖先。
最小例子:
struct Base {
virtual ~Base() {}
virtual void foo() = 0;
};
struct Derived : public virtual Base {
void foo() override {
::std::cout << "Hello from Derived" << ::std::endl;
}
};
int main() {
Derived d{};
store(d);
// .....
poly_load<Base>().foo();
}
答案 1 :(得分:1)
实际上,您应该在type_info
个实例上使用相等测试。
reinterpret_cast
不提供任何保证,除非转换回确切的原始类型。甚至
Derived* d = get_derived();
Base* b = reinterpret_cast<Base*>(d);
不会给出正确的结果(如果Base
子对象未存储在Derived
内的偏移零处,这仅适用于标准布局类型。
完整规则见5.2.10节:
可以将对象指针显式转换为不同类型的对象指针。当对象指针类型的prvalue
v
转换为对象指针类型“指向cv
T
的指针”时,结果为static_cast<
< EM>cv
T*>(static_cast<
cv
void*>(v))
。将“指向T1
”的类型的prvalue转换为“指向T2
”的类型(其中T1
和T2
是对象类型,{{T2
的对齐要求1}}不比T1
更严格,并且返回原始类型会产生原始指针值。
只有static_cast
和dynamic_cast
可以执行基本子对象调整,当任何一种类型为void*
时(在类型擦除之后),这些调整都不会启动。
但是,看起来Boost开发人员已经解决了所有困难。见boost::variant::polymorphic_get
答案 2 :(得分:0)
据我所知,确定推导的唯一可靠且可移植的方法是在try catch块中使用dynamic_cast。如果它是可投射的,它不会抛出bad_cast异常。在您的商店例程中进行此测试,如果它不抛出,则存储数据,否则将其设置为NULL。不要忘记在你的装载程序中检查它。
答案 3 :(得分:0)
异常的自我回答非常酷。这是一个独立的版本:
class polymorphic_erasure {
std::function< void() > throw_self;
public:
template< typename static_type >
polymorphic_erasure( static_type & o )
: throw_self( [ & o ] { throw & o; } )
{}
polymorphic_erasure()
: throw_self( [] { throw std::bad_cast(); } )
{}
template< typename want_type >
want_type & get() const {
try {
throw_self();
} catch ( want_type * result ) {
return * result;
} catch ( ... ) {}
throw std::bad_cast();
}
};
演示:http://coliru.stacked-crooked.com/a/a12114a210c77a45
但请注意,您无法使用多态基本类型分配,然后get
派生对象 - 您仍然需要dynamic_cast
。而且这里没有RTTI或多态。 (也许它需要一个不同的名称。)虽然异常确实对类使用RTTI,但polymorphic_erasure
只抛出指针。基于异常的功能是互补的:它将对象分类为类型层次结构,仅此而已。
答案 4 :(得分:-1)
这样的事情:
void* data;
class Wrapper{ virtual ~Wrapper()=0; };
template<typename T> class SpecificWrapper: public Wrapper {
public:
T* value;
Wrapper(T* ptr): value(ptr){}
~Wrapper() {}
}
template<typename D>
void store(D&& object)
{
Wrapper* wrapper = new SpecificWrapper<D>(&object);
if(data!= ::std::nullptr)
delete reinterpret_cast<Wrapper*>(data);
data = (void*)wrapper;
}
template<typename B>
B& load()
{
//always safe because we know type being correct
Wrapper *w = reinterpret_cast<Wrapper*>(data);
SpecificWrapper<B> * w1 = dynamic_cast<SpecificWrapper<B>>(w);
if(w1==::std::nullptr) throw ::std::bad_cast{};
return w1->value;
}
这个想法是使用包装器类型层次结构来进行类型擦除,同时保留类型信息。通过这种方式,您可以静态地确定data
变量的类型,即使它被声明为void*
也始终是顶级Wrapper
类,允许您始终执行安全强制转换。你需要注意包装器对象的生命周期......