我有一个
std::expected
的自定义实现,但是有一个类型擦除错误。它使预期类型看起来类似于异常。我们可以使用下一个代码:
Expected<int> V = 123;
V.SetError(std::string("Error occurred"));
当我们尝试从
Expected
获取值时,我们可以将存储的值输出到屏幕(如果它支持 std::string
或存储在 Expected
中的其他类型的某些函数重载)。
如果我们不想使用异常,我认为这是一个很好的做法。
但是,由其父类处理错误也很好,类似于
try
/catch
:
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_cast
将存储的错误(如 void*
)转换为 MathError*
.
好的,我可以使用静态模板函数(1 个实例化类型 = 1 个类型 ID)为用作错误的每种类型创建一个单独的类型标识符。
但是,在这种情况下,如果它们的 ID 相同,我只能转换为确切的类型。也许像
std::bases
这样的东西会帮助我(我可以枚举所有类并列出它们的 ID),但标准中没有此功能。
那么,有机会吗?
我找到了不用 RTTI 解决它的好方法
我们可以声明可以使用这样的
expected
接口捕获的类。
首先声明
RuntimeError
的基类,添加返回自身类型id的静态函数
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> {};
现在,当我们将错误设置为
expected
时,我们可以发送每个错误父类的所有ids:
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();
}