我们可以在运行时确定两个 type_info 是否可以施放吗?
Can we determine at runtime if two type_info's would be castable?
有没有办法从两个const ::std::type_info
对象中判断出D所描述的类型是否是从类型B派生出来的?
我问是因为我想擦除我得到的对象的类型,但稍后能够检查它是否可以安全地提升。
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
.
据我所知,确定推导的唯一可靠且可移植的方法是在 try catch 块中使用 dynamic_cast。如果它是可转换的,它不会抛出 bad_cast 异常。在您的存储例程中执行此测试,如果它不抛出,则存储数据,否则将其设置为 NULL。不要忘记在加载例程中检查它。
像这样的事情怎么样:
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
class,让你始终执行安全演员表。不过你需要注意包装器对象的生命周期...
实际上,您应该在 type_info
个实例上使用相等性测试。
reinterpret_cast
不提供任何保证,除非转换回确切的原始类型。甚至
Derived* d = get_derived();
Base* b = reinterpret_cast<Base*>(d);
将不会给出正确的结果(如果 Base
子对象未存储在 Derived
内的零偏移处,这仅对标准布局类型有保证)。
完整规则见第 5.2.10 节:
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v
of object pointer type is converted to the object pointer type "pointer to cv
T
", the result is static_cast<
cv
T*>(static_cast<
cv
void*>(v))
. Converting a prvalue of type "pointer to T1
" to the type "pointer to T2
” (where T1
and T2
are object types and where the alignment requirements of T2
are no stricter than those of T1
) and back to its original type yields the original pointer value.
只有 static_cast
和 dynamic_cast
可以执行基础子对象调整,当任一类型为 void*
(类型擦除后)时,这些调整不会生效。
但是,Boost 开发人员似乎已经解决了所有困难。参见 boost::variant::polymorphic_get
我找到了一种让编译器和内部机制为我解决问题的方法。我对交叉编译没有问题,在那种情况下 ::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 子句 B*
捕获该异常,其中 B
是 [=19= 的任何祖先].
最小示例:
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();
}
有例外的自我回答很酷。这是一个独立的版本:
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
只抛出指针。基于异常的功能是互补的:它将对象分类为类型层次结构,仅此而已。
有没有办法从两个const ::std::type_info
对象中判断出D所描述的类型是否是从类型B派生出来的?
我问是因为我想擦除我得到的对象的类型,但稍后能够检查它是否可以安全地提升。
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
.
据我所知,确定推导的唯一可靠且可移植的方法是在 try catch 块中使用 dynamic_cast。如果它是可转换的,它不会抛出 bad_cast 异常。在您的存储例程中执行此测试,如果它不抛出,则存储数据,否则将其设置为 NULL。不要忘记在加载例程中检查它。
像这样的事情怎么样:
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
class,让你始终执行安全演员表。不过你需要注意包装器对象的生命周期...
实际上,您应该在 type_info
个实例上使用相等性测试。
reinterpret_cast
不提供任何保证,除非转换回确切的原始类型。甚至
Derived* d = get_derived();
Base* b = reinterpret_cast<Base*>(d);
将不会给出正确的结果(如果 Base
子对象未存储在 Derived
内的零偏移处,这仅对标准布局类型有保证)。
完整规则见第 5.2.10 节:
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue
v
of object pointer type is converted to the object pointer type "pointer tocv
T
", the result isstatic_cast<
cv
T*>(static_cast<
cv
void*>(v))
. Converting a prvalue of type "pointer toT1
" to the type "pointer toT2
” (whereT1
andT2
are object types and where the alignment requirements ofT2
are no stricter than those ofT1
) and back to its original type yields the original pointer value.
只有 static_cast
和 dynamic_cast
可以执行基础子对象调整,当任一类型为 void*
(类型擦除后)时,这些调整不会生效。
但是,Boost 开发人员似乎已经解决了所有困难。参见 boost::variant::polymorphic_get
我找到了一种让编译器和内部机制为我解决问题的方法。我对交叉编译没有问题,在那种情况下 ::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 子句 B*
捕获该异常,其中 B
是 [=19= 的任何祖先].
最小示例:
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();
}
有例外的自我回答很酷。这是一个独立的版本:
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
只抛出指针。基于异常的功能是互补的:它将对象分类为类型层次结构,仅此而已。