当 C++ 函数不 'move from' 右值引用参数时,这是合理的方法吗?
Is it reasonable approach when C++ function does not 'move from' rvalue reference argument?
假设 class Object
满足 MoveConstructible 和 MoveAssignable 的要求,但不满足 的要求]CopyConstructible 或 CopyAssignable(即包含 unique_ptr 的 PIMPL)。然后我有两个消费者:
bool consumer1(Object && arg) {
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return true;
}
return false;
}
bool consumer2(Object &&arg) {};
int main() {
Object arg;
// some arg initialization here
if(!consumer1(std::move(arg)))
consumer2(std::move(arg));
}
即我想将 Object 的一个实例移动到consumer1(),如果它由于某种原因不接受它,我将实例移动到consumer2()。
或者,我可以检查实例的状态(即它是否被移动)而不是依赖 return 值。
虽然我知道that
std::move doesn’t move anything.
不过两个动作我还是觉得不舒服
这是一个合理的做法吗?有没有更简单的方法来实现相同的想法?
如您所写,当 1
returns 为 false 时,arg
仍处于有效状态,因此您的代码没有任何问题(即使它不是' t,还是不会有什么问题的)。
这个问题比较主观,但我会尽量给出明确的回答。
虽然您编写的代码技术上没有任何错误,因为代码将编译并且 运行 正确,来自 higher-level这是 design-smell.
对对象 std::move
的明显重复调用限制了开发人员推断该对象生命周期的能力,这会降低可读性。静态分析工具也可能将此类移动标记为“use-after-move”,尽管事实上该对象并未实际就位(从技术上讲,这不会是错误的)。
我想到了几个清理选项,但完全是可选的,并且取决于您的实际 use-case:
- 将
std::move
的条件分解为单独的检查,或者
- 使用后备模式
打破条件
如果您可以突破 consumer1
消费 arg
的条件,那么您可以在首次尝试消费之前明确测试这一点——允许您有两个明显不同的分支 [= =16=]独立:
if(can_consume(arg)) {
consumer1(std::move(arg));
} else {
consumer2(std::move(arg));
}
回退模式
另一种可能的重构是使用回退模式,在这种模式下,您设计的代码可以在失败时发生 dependency-injected“回退”。
如果这都是简单的原始函数,一个简单的例子可以是:
using FallbackFunction = void(*)(Object&&);
void consume1_or(Object&& arg, FallbackFunction function)
{
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return
}
// fallback on failure
(*function)(std::move(arg));
}
...
// use
consume1_or(std::move(arg), &consume2);
然而,这也可以扩展到一个完整的接口,其中扩展 Consumer
的东西在构造时也接受 Consumer
以在失败时将对象传递给。
假设 class Object
满足 MoveConstructible 和 MoveAssignable 的要求,但不满足 的要求]CopyConstructible 或 CopyAssignable(即包含 unique_ptr 的 PIMPL)。然后我有两个消费者:
bool consumer1(Object && arg) {
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return true;
}
return false;
}
bool consumer2(Object &&arg) {};
int main() {
Object arg;
// some arg initialization here
if(!consumer1(std::move(arg)))
consumer2(std::move(arg));
}
即我想将 Object 的一个实例移动到consumer1(),如果它由于某种原因不接受它,我将实例移动到consumer2()。
或者,我可以检查实例的状态(即它是否被移动)而不是依赖 return 值。
虽然我知道that
std::move doesn’t move anything.
不过两个动作我还是觉得不舒服
这是一个合理的做法吗?有没有更简单的方法来实现相同的想法?
如您所写,当 1
returns 为 false 时,arg
仍处于有效状态,因此您的代码没有任何问题(即使它不是' t,还是不会有什么问题的)。
这个问题比较主观,但我会尽量给出明确的回答。
虽然您编写的代码技术上没有任何错误,因为代码将编译并且 运行 正确,来自 higher-level这是 design-smell.
对对象 std::move
的明显重复调用限制了开发人员推断该对象生命周期的能力,这会降低可读性。静态分析工具也可能将此类移动标记为“use-after-move”,尽管事实上该对象并未实际就位(从技术上讲,这不会是错误的)。
我想到了几个清理选项,但完全是可选的,并且取决于您的实际 use-case:
- 将
std::move
的条件分解为单独的检查,或者 - 使用后备模式
打破条件
如果您可以突破 consumer1
消费 arg
的条件,那么您可以在首次尝试消费之前明确测试这一点——允许您有两个明显不同的分支 [= =16=]独立:
if(can_consume(arg)) {
consumer1(std::move(arg));
} else {
consumer2(std::move(arg));
}
回退模式
另一种可能的重构是使用回退模式,在这种模式下,您设计的代码可以在失败时发生 dependency-injected“回退”。
如果这都是简单的原始函数,一个简单的例子可以是:
using FallbackFunction = void(*)(Object&&);
void consume1_or(Object&& arg, FallbackFunction function)
{
if(arg ...) { // arg satisfies some condition
Object b {std::move(arg)};
// some processing
return
}
// fallback on failure
(*function)(std::move(arg));
}
...
// use
consume1_or(std::move(arg), &consume2);
然而,这也可以扩展到一个完整的接口,其中扩展 Consumer
的东西在构造时也接受 Consumer
以在失败时将对象传递给。