C++ 存在错误代码时的Out参数与NRVO
我们有一个广泛使用out参数的代码库,因为每个函数都可能因某些错误枚举而失败。 这变得非常混乱,代码有时无法读取 我想消除这种模式,采用更现代的方法 目标是转变:C++ 存在错误代码时的Out参数与NRVO,c++,optimization,error-handling,nrvo,C++,Optimization,Error Handling,Nrvo,我们有一个广泛使用out参数的代码库,因为每个函数都可能因某些错误枚举而失败。 这变得非常混乱,代码有时无法读取 我想消除这种模式,采用更现代的方法 目标是转变: error_t fn(param_t *out) { //filling 'out' } param_t param; error_t err = fn(¶m); 变成类似于: std::expected<error_t, param_t> fn() { param_t ret;
error_t fn(param_t *out) {
//filling 'out'
}
param_t param;
error_t err = fn(¶m);
变成类似于:
std::expected<error_t, param_t> fn() {
param_t ret;
//filling 'ret'
return ret;
}
auto& [err, param] = fn();
std::预期的fn(){
参数ret;
//填充“ret”
返回ret;
}
自动&[err,param]=fn();
以下问题是为了说服我自己和其他人这一改变是最好的:
预期的实现[可能使用表示错误是否完全消失的布尔值]
首先,有几个假设:
一般不,但是像C++中的所有东西一样,有边缘情况可以出现。最大化缓存一致性的显式内存布局对于“指针返回”来说是一个很好的用例
假设发生了NRVO,生成的程序集中是否有重大更改(假设优化的预期实现[可能使用表示错误是否完全消失的布尔值]) 这个问题对我来说没有多大意义<代码>预期值的行为更像是一个变量
而不是一个元组
,因此“表示错误是否完全消失的布尔值”实际上没有意义
也就是说,我认为我们可以使用std::variant来估计:
在我看来,这是“不同的”,但不一定是更好或更糟。假设您只询问非内联场景是否安全?因为一旦内联开始,它基本上是100%等价的。@Frank它关心的是是否根据内联的假设来构造代码。因此,“当内联不能发生时,我可以依赖它吗”(比如说有一天我切换编译器,我的代码会变慢吗?)'std::expected代表了一个实质性的api更改(为了更好),这使得重构有点不等价。切换到std::tuple将是一个更简单的1对1匹配。具体来说,将性能与调用忽略错误的代码进行比较是不公平的。@Frank我没有列出使用“expected”的所有优点,因为它实际上与问题无关。事实上,其中一个考虑因素是不允许忽略错误(通过nodiscard警告)。这与您关心比较这两种方法以及比较生成的程序集有关。但是,比较仅对使用错误的代码公平,并且只传递默认初始化的
param\t
。所以我只想在得出答案之前确定这些假设是有效的。谢谢你的回答,我有几个问题:首先,为什么你的假设3是必要的?如果我们在这两种情况下使用相同的部分初始化,我看不出有任何区别。其次,对于variant
注释,我认为我们通常只在调用函数后立即测试是否有错误,因此可以优化variant
的“标记”。@Omerosler假设#3是必要的,因为否则,可以多次重用部分烘焙的结果,只付一次费用。就变量而言,假设1主要涵盖了这一点。由于生成函数的代码不知道结果将如何使用,因此它别无选择,只能生成标记并将其存储在结果中。但是,(在预期的情况下,
),除非您的错误类型设法小于bool
(祝您好运),否则您将领先,因为错误和有效负载之间的存储重叠。我明白您对#3的意思,事实上这就是用例。至于标记,如果您永远无法在err5or和return之间切换(标记是const
),那么它就不同于完整的变量,因此我认为编译器可以完全省略这个变量(因为它可以看到它是如何完整地使用的)