我们可以在运行时确定两个type_info是否可以转换?

时间:2015-04-04 22:32:06

标签: c++ dynamic-cast typeid typeinfo

有没有办法从两个const ::std::type_info个对象中确定,如果D描述的类型是从类型B派生的,那么我们将它们命名为BD

我问,因为我想要删除我得到的对象的类型,但稍后可以检查它是否可以安全地提升。

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

5 个答案:

答案 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*捕获该异常,其中BD的任何祖先。

最小例子:

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”的类型(其中T1T2是对象类型,{{T2的对齐要求1}}不比T1更严格,并且返回原始类型会产生原始指针值。

只有static_castdynamic_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类,允许您始终执行安全强制转换。你需要注意包装器对象的生命周期......