为啥非多态 typeid 需要 RTTI?

Posted

技术标签:

【中文标题】为啥非多态 typeid 需要 RTTI?【英文标题】:Why is RTTI needed for non-polymorphic typeid?为什么非多态 typeid 需要 RTTI? 【发布时间】:2020-07-28 11:06:14 【问题描述】:

我有以下代码:

template<typename T>
class genericHandlerpublic: using evt_t = T;;

template<typename T>
class specialHandler : public genericHandler<T>   /* more stuff */ ;

int main(int argc, char *argv[]) 
    
    std::any any_var = specialHandler<int>;
    
    auto f = [&any_var](auto evtHandler) 
        using EventType = typename std::remove_reference<decltype(evtHandler)>::type ::evt_t;
        if(any_var.type() == typeid(EventType))  std::cout << "yes" << std::endl;  else  std::cout << "no" << std::endl; 
    ;

    auto h = specialHandler<int> ;
    f(h);

Try it on Coliru

当被调用时,evtHandler 是非多态派生类型specialHandler。根据cppreference,我们有:

当应用于多态类型的表达式时,计算 typeid 表达式可能涉及运行时开销(虚拟表 查找),否则在编译时解析 typeid 表达式。

当我使用 gcc 和 -fno-rtti 编译时,我收到以下错误消息:

不能将“typeid”与“-fno-rtti”一起使用

RTTI 是运行时类型信息,在编译时可以推导出的非多态类型id 的情况下不需要。我错过了什么吗?

【问题讨论】:

你的类型是非多态的,但不幸的是你不知道std::any的内部结构。 比较open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1105r1.html。我可以确认它适用于 VS2017(并且禁用 rtti),但在 gcc 主干和 clang 主干上失败。 GCC 的 std::any 在没有 RTTI 的情况下也能正常工作,只需使用 any_cast 检查它是否拥有该类型,而不是使用 typeid,例如coliru.stacked-crooked.com/a/f68faa89814ea711 在回答下的评论中,您写了一些关于依赖周期的内容。请描述这个问题,因为显然这是您问题的实际根源,并且有很多技巧可以打破这种循环。 【参考方案1】:

你问了两个问题。

    RTTI是运行时类型信息,在非多态typeid的情况下应该不需要...

    但这是编译器开关,不是语言功能,所以你应该检查compiler documentation:

    -fno-rtti

    禁止生成有关每个具有虚函数的类的信息,以供 C++ 运行时类型识别功能(dynamic_casttypeid)使用。 如果您不使用该语言的这些部分,则可以使用此标志节省一些空间。请注意,异常处理使用相同的信息,但它会根据需要生成它。 dynamic_cast 运算符仍可用于不需要运行时类型信息的强制转换,即强制转换为 void * 或明确的基类。

    (我的重点)。

    所以开关禁用整个 typeinfo 系统以节省空间。如果您想要typeinfo(或者您想要使用使用typeinfo 的标准库工具),则不要使用编译器选项显式禁用typeinfo

    ...可以在编译时推导出的非多态类型标识

    编辑,正如 Wakely 先生指出的那样,问题在于您在自己的代码中明确使用了 typeid

    虽然std::any 可能能够在不使用 RTTI 的情况下从***对象中擦除存储的类型,但这取决于实现,并且它肯定如果不使用typeid/typeinfo 你告诉 GCC 不要生成。

哦,我忘了

    我错过了什么吗?

    是的,问你的实际问题,即

    我删除它是因为我需要解决循环模板依赖性,遗憾的是我不能在这里“简单地不删除它”。 我是否有任何不使用 RTTI 的替代方案

    当然,类型擦除不依赖于 RTTI。

    只有 自动 类型的擦除取决于 RTTI,如上所述,并非全部。您可以避免 std::any::type() 或手动编写自己的可区分联合 - 您需要枚举类型,但只有枚举本身需要对 DU 的所有用户可见。

【讨论】:

我正在删除它,因为我需要解决循环模板依赖性,遗憾的是我不能在这里“简单地不删除它”。我有任何不使用 RTTI 的替代方案吗? @WernerHenze 但它也说“如果你不使用语言的那些部分” - 意思是 dynamic_cast 除非特别允许和 typeid - 然后你可以使用 -fno-rtti旗帜。暗示如果根本需要typeid,它就不能使用。 @Magix std::anytypeid 和 RTTI 相当相关。我建议提出另一个关于潜在替代方案的问题,并提供更多背景说明为什么 std::any 似乎是合适或必要的。 答案明确回答了为什么typeid 不起作用。这是因为 GCC 标志禁用了整个 typeinfo 系统。我真的不确定我能清楚多少。如果您不喜欢 GCC 文档本身,请接受他们。 @aschepler std::any 根本与 RTTI 无关,只有 std::any::type 必须 使用它。正如我在上面评论的那样,GCC 的 std::any 在没有 RTTI 的情况下也可以正常工作,例如coliru.stacked-crooked.com/a/f68faa89814ea711【参考方案2】:

可以更改您的代码而不使用std::any::type()。在这种情况下,一切都按预期工作(在您的代码中修复了一些其他问题之后):

#include <iostream>
#include <any>

template<typename T>
class genericHandlerpublic: using evt_t = T;;

template<typename T>
class specialHandler : public genericHandler<T>   /* more stuff */ ;

int main(int argc, char *argv[]) 
    
    std::any any_var = specialHandler<int>;
    
    auto f = [&any_var](auto evtHandler) 
        using EventType = typename std::remove_reference<decltype(evtHandler)>::type ::evt_t;
        std::cout << (std::any_cast<specialHandler<EventType>>(&any_var) != nullptr ? "yes" : "no") << std::endl;
    ;

    auto h = specialHandler<int> ;
    f(h);
    f(specialHandler<double>);

在禁用 RTTI 时工作:http://coliru.stacked-crooked.com/a/20855b15701605f1

【讨论】:

以上是关于为啥非多态 typeid 需要 RTTI?的主要内容,如果未能解决你的问题,请参考以下文章

C++ RTTI详解

C++ RTTI详解

RTTI

C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理

运行时类型识别RTTI

typeid详解