使用std::move和std::optional来更清晰地处理错误

在这个实习里也用到了基于C++的私有框架。这个框架虽说是基于C++17的,但是错误处理用的是错误码和参数位置返回出去的做法。可能和框架需要接入gRPC有关。

自己这边需要用框架来做一些简易的操作,实现一个自己的proxy服务器,也就想到了用std::optional来重新处理一下这个错误流程的想法了。

大概的思路是,因为自己写的方法会被框架托管,所以自己实现的每个方法都返回一个optional的值,在主函数里把这些optional串接起来,最后主函数返回一个错误码。这样,虽然整个流程还是符合框架的定义,可以被完美地托管,但是自己编码的部分就是用optional做的,也就符合现代C++的编码风格了。

实现的方法大概是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
std::optional<SRef<Platform::data::Scene>> ProxyGrpcService::makeFetch() const
{
SRef<Platform::data::Scene> scene = boost::make_shared<Platform::data::Scene>();
switch (_pipeline->getRemoteScene(scene))
{
case Platform::ReturnCode::_SUCCESS:
return scene;
default:
EMIT_ERROR("Some error message...");
return std::nullopt;
}
}

这里我本来想用try-catch语句的,但是考虑到在C++里面这并不是被广泛接受的写法,而且上游的gRPC函数本就是返回错误码的,所以就改用switch语句了。之所以用switch而不是if也是想强调「根据返回值来switch」的思路。

主函数里面大概是这样的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (auto value = makeFetch(); !value.has_value())
{
EMIT_ERROR("Some error message...");
return GrpcError("Some error description");
}
else
{
const auto scene = std::move(value);

...

// 这里可以用 scene->get() 来取出 scene 里面的值
// 然后就可以像正常的指针一样去获取里面的数据了

... // 关闭上游的 gRPC 连接

switch (ProxyGrpcService::stop())
{
case Started: throw Platform::Exception {"Some error message..."};
case Failed: return GrpcError("Some error description");
case Succeeded: break;
}

... // 通过函数传参把值返回出去,并且返回正常的状态码

*response = *data;
return grpc::Status::OK;
}

这里直接用了has_valueget,一方面是对于简单的fetch来说已经够用了,另外一方面也是因为C++里面要等到C++23才有monadic的optional操作。

除此之外,这里也用到了std::move函数,作用是在不进行深拷贝的前提下把optional也就是这里的value里面的值取出来(更准确地说是移动)到scene变量里。这里就不细说「转移所有权」或者「右值引用」等高深细节了。

之所以用std::move而不是调用框架自带的拷贝函数,一个很重要的原因也是因为3D渲染中,需要处理的场景可能会很大,所以对应的数据也会非常庞大。直接复制的开销还是不可忽视的。

总之大概就这样了。


使用std::move和std::optional来更清晰地处理错误
http://inori.moe/2022/11/06/handle-errors-with-stdmove-and-stdoptional/
作者
inori
发布于
2022年11月6日
许可协议