我们可以在运行时确定两个 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*也始终是顶层Wrapperclass,让你始终执行安全演员表。不过你需要注意包装器对象的生命周期...

实际上,您应该在 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_castdynamic_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 只抛出指针。基于异常的功能是互补的:它将对象分类为类型层次结构,仅此而已。