是否可以在没有 RTTI 的情况下向上发送擦除类型?

Is that possible to upcast erased type without RTTI?

提问人:Artem Selivanov 提问时间:4/19/2023 最后编辑:Remy LebeauArtem Selivanov 更新时间:4/20/2023 访问量:129

问:

我有一个自定义实现,但有一个类型擦除错误。它使预期的类型看起来类似于异常。我们可以使用下面的代码:std::expected

Expected<int> V = 123;
V.SetError(std::string("Error occurred"));

当我们尝试从 中获取值时,我们可以将存储的值输出到屏幕(如果它支持某些函数重载或存储在 中的其他类型)。Expectedstd::stringExpected

如果我们不想使用异常,我认为这是一个很好的做法。

但是,通过其父类处理错误也很好,类似于 /:trycatch

struct MathError{};
struct ZeroDivisionError : MathError{};

Expected V = 321;
V.SetError(ZeroDivisionError{});

if (auto Error = V.Catch<MathError>())
{
   // we go here if error is subclass of MathError
}

但是,我已经在我的项目中禁用了 RTTI,现在我无法将存储的错误(作为)上报到 .dynamic_castvoid*MathError*

好的,我可以使用静态模板函数(1 个实例化类型 = 1 个类型 id)为每个用作错误的类型创建一个单独的类型标识符。

但是,在这种情况下,只有当它们的 ID 相同时,我才能转换为确切的类型。也许类似的东西会对我有所帮助(我可以枚举所有类并列出它们的 id),但此功能在标准中不可用。std::bases

那么,有机会吗?

C++ 异常 RTTI 类型擦除 STD-预期

评论

2赞 Öö Tiib 4/19/2023
上转换是指将子对象类型转换为父对象。向下转换提供将父对象转换为子对象。然而,从虚空*投射两者都不是。我们必须完全按照它被强制转换为 void* 的类型进行强制转换,无论是否有 RTTI。
0赞 Jesper Juhl 4/19/2023
为什么要禁用 RTTI?是的,有一个小尺寸的开销和(如果使用)一个小的运行时开销。但在现代硬件上,这一切都可以忽略不计。
3赞 Pete Becker 4/19/2023
换个说法,您的问题是,是否可以编写基于对象的运行时类型做出决策的代码,而无需编译器生成的运行时类型信息。确定;你必须自己完成所有的工作。这很难,而且容易出错。为什么要禁用 RTTI?
0赞 joergbrech 4/19/2023
出于好奇,为什么一开始就删除了错误类型?它不在,这种类型擦除是问题的根源。std::expected
0赞 Artem Selivanov 4/20/2023
@JesperJuhl 在框架上下文中可能不建议使用 RTTI。例如:在虚幻引擎项目中,RTTI和异常通常被禁用。

答:

0赞 Artem Selivanov 4/20/2023 #1

我找到了很好的解决方案来解决没有RTTI的问题

我们可以声明可以使用此类接口捕获的类。expected

首先,声明 的基类并添加返回自身类型 id 的静态函数。RuntimeError

class RuntimeError
{
    static set<size_t> GetBaseIds()
    {
        return { TypeId::GetTypeId<RuntimeError>() };
    }
}

接下来,创建一个模板类,该类将向中间父级添加一些东西:

template<typename Parent>
class DeriveError : Parent
{
   static set<size_t> GetBaseIds()
   {
      set<size_t> BaseIds = Parent::GetBaseIds();
      BaseIds.insert(TypeId::GetTypeId<Parent>());
      return BaseIds ;
   }
}

现在我们可以轻松地从 RuntimeError 中推导出错误。

class MathError : DeriveError<RuntimeError> {};
class ZeroDivisionError : DeriveError<MathError> {};

现在,当我们将 error 设置为 时,我们可以发送每个错误父类的所有 id:expected

    template<typename E>
    void SetError(E Error)
    {
        // Erased error handler
        ErrorHandler = make_unique<ErrorHandler<E>>(Error);

        if constexpr (is_base_of_v<RuntimeError, E>)
        {
            set<size_t> Bases = E::GetBaseIds();
            Bases.Add(TypeId::GetTypeId<E>());
            // Send parent ids to error handler
            ErrorHandler->SetBases(Bases);
        }
    }

捕获方法:

    // avoid catching if not derived from RuntimeError
    template<typename E>
    typename enable_if<is_base_of_v<RuntimeError, E>, const E*>::type
    Catch()
    {
        const int32 ErrorTypeId = TypeId::GetTypeId<E>();
        // Error handler can return NULL if ErrorTypeId not matches any type id
        return static_cast<const E*>(ErrorHandler->TryCatchErrorByTypeId(ErrorTypeId));
    }

现在我们可以设置和捕获错误:

Expected Value = 1234;
Value.SetError(ZeroDivisionError>());

if (auto Error = Value.Catch<RuntimeError>())
{
    cout << "Error caught: " << Error.ToString();
}